registry  /  terminalhire  /  0.19.1

terminalhire@0.19.1

Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.

AI Security Review

scanned 4h ago · by lpm-firewall-ai

LPM treats this as warn-only first-party agent extension lifecycle risk. No confirmed malicious install-time attack surface. The package is an AI/job-matching CLI that can, after explicit user prompts, modify Claude spinner/statusLine settings and register a terminalhire MCP server in supported hosts.

Static reason
One or more suspicious static signals were detected.; previous stored version diff introduced dangerous source
Trigger
User runs terminalhire init, install.js, statusline-install.js, or MCP setup commands
Impact
Guarded writes to Claude/Cursor/Gemini agent configuration and package-owned ~/.terminalhire state; network use for login, job index, leads, chat, and sync when commands are invoked.
Mechanism
consent-gated AI-agent extension and local profile/job matching CLI
Rationale
Source inspection shows the install lifecycle is print-only; the AI-agent config changes and MCP registration are user-invoked and consent-gated, but they still extend broad agent surfaces and warrant a warning rather than a block. I did not find unconsented lifecycle mutation, credential exfiltration, destructive behavior, or remote code execution.
Evidence
package.jsonpostinstall.jsinstall.jsstatusline-install.jsdist/bin/jpi-init.jsdist/bin/mcp-config.jsdist/bin/spinner.jsdist/bin/jpi-statusline-launch.jsdist/bin/jpi-dispatch.js~/.claude/settings.json~/.terminalhire/config.json~/.terminalhire/statusline-launch.js~/.terminalhire/statusline-foreign.json~/.terminalhire/profile.enc~/.terminalhire/github-token.enc~/.cursor/mcp.json~/.gemini/settings.json
Network endpoints12
terminalhire.comgithub.com/login/device/codegithub.com/login/oauth/access_tokenapi.github.comboards-api.greenhouse.ioapi.ashbyhq.comapi.lever.cohimalayas.appweworkremotely.comhn.algolia.comapi.opire.devapply.workable.com

Decision evidence

public snapshot
AI called this Suspicious at 86.0% confidence as Dangerous Capability with medium false-positive risk.
Evidence for warning
  • install.js can write ~/.claude/settings.json spinnerVerbs/spinnerTipsOverride after typed yes
  • statusline-install.js can copy launcher to ~/.terminalhire/statusline-launch.js and set ~/.claude/settings.json statusLine after typed yes
  • dist/bin/mcp-config.js can merge terminalhire MCP entries into ~/.cursor/mcp.json or ~/.gemini/settings.json after prompts
  • dist/bin/jpi-init.js orchestrates GitHub login, job cache fetch, Claude spinner/statusLine setup, and optional MCP setup
  • dist/bin/jpi-dispatch.js includes commands that read ~/.claude/projects and can sync/profile/lead to terminalhire.com with user command flow
Evidence against
  • package.json postinstall runs only postinstall.js, which prints a notice and exits without file writes or network
  • install.js and statusline-install.js are guarded by interactive typed consent and no-op/abort on noninteractive input
  • MCP config writer only touches detected Cursor/Gemini configs after per-host y/yes prompts and prints other host snippets instead of writing
  • Network endpoints are package-aligned or expected public sources: terminalhire.com, github.com/api.github.com, job-board APIs
  • Local tokens/profile are stored under ~/.terminalhire with encryption helpers; no import-time credential harvesting found
  • No lifecycle-triggered unconsented mutation of foreign AI-agent control surfaces found
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsEvalFilesystemNetworkShell
Supply chain
HighEntropyStringsObfuscatedUrlStrings
ManifestNo manifest risk signals triggered.
scanned 48 file(s), 5.44 MB of source, external domains: 127.0.0.1, api.ashbyhq.com, api.github.com, api.lever.co, api.opire.dev, apply.workable.com, boards-api.greenhouse.io, github.com, himalayas.app, hn.algolia.com, jobs.ashbyhq.com, jobs.lever.co, json-schema.org, news.ycombinator.com, raw.githubusercontent.com, spec.openapis.org, stackoverflow.com, terminalhire.com, tools.ietf.org, weworkremotely.com, www.safaribooksonline.com, www.w3.org

Source & flagged code

12 flagged · loading source
package.jsonView file
scripts.postinstall = node ./postinstall.js
High
Install Time Lifecycle Scripts

Package defines install-time lifecycle scripts.

package.jsonView on unpkg
scripts.postinstall = node ./postinstall.js
Medium
Ambiguous Install Lifecycle Script

Install-time lifecycle script is not statically allowlisted and needs review.

package.jsonView on unpkg
dist/bin/jpi-chat.jsView file
5086}); L5087: import { spawn } from "child_process"; L5088: function openInBrowser(url) {
High
Child Process

Package source references child process execution.

dist/bin/jpi-chat.jsView on unpkg · L5086
128Cross-file remote execution chain: dist/bin/jpi-chat.js spawns dist/bin/jpi-chat-read.js; helper contains network access plus dynamic code execution. L128: { id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] }, L129: { id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] }, L130: { id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] }, ... L220: { id: "microservices" }, L221: { id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] }, L222: { id: "realtime", synonyms: ["real-time"] }, ... L923: for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { L924: const n1 = asciiToBase16(hex.charCodeAt(hi)); L925: const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); ... L2300: // It's faster, but should only be used when you don't care about L2301: // an exposed private key e.g. sig verification. L2302: // Does NOT allow scalars higher than CURVE.n.
High
Cross File Remote Execution Context

Source spawns a local helper that also contains network and dynamic execution context; review data flow before blocking.

dist/bin/jpi-chat.jsView on unpkg · L128
dist/bin/jpi-statusline-launch.jsView file
49if (!cmd) return ""; L50: const r = spawnSync(cmd, { shell: true, input, timeout: 4e3 }); L51: return firstLine(r.stdout);
High
Shell

Package source references shell execution.

dist/bin/jpi-statusline-launch.jsView on unpkg · L49
dist/bin/jpi-dispatch.jsView file
matchType = previous_version_dangerous_delta matchedPackage = terminalhire@0.19.0 matchedIdentity = npm:dGVybWluYWxoaXJl:0.19.0 similarity = 0.854 summary = stored previous version shares package body but lacks this dangerous source file
Critical
Previous Version Dangerous Delta

This package version adds a dangerous source file absent from the previous stored version; route for source-aware review.

dist/bin/jpi-dispatch.jsView on unpkg
25761sourceCode = this.opts.code.process(sourceCode, sch); L25762: const makeValidate = new Function(`${names_1.default.self}`, `${names_1.default.scope}`, sourceCode); L25763: const validate = makeValidate(this, this.scope.get());
Low
Eval

Package source references a known benign dynamic code generation pattern.

dist/bin/jpi-dispatch.jsView on unpkg · L25761
dist/bin/jpi-init.jsView file
347try { L348: const { maybePromptPeerConnect } = await import(pathToFileURL(resolveScript("peer-connect-prompt")).href); L349: let login;
Medium
Dynamic Require

Package source references dynamic require/import behavior.

dist/bin/jpi-init.jsView on unpkg · L347
dist/bin/jpi.jsView file
5import { isatty } from "tty"; L6: import net from "net"; L7: import { join } from "path"; ... L9: import { fileURLToPath } from "url"; L10: import { spawn } from "child_process"; L11: var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join(homedir(), ".terminalhire"); L12: var INDEX_CACHE_FILE = join(TERMINALHIRE_DIR, "index-cache.json");
High
Same File Env Network Execution

A single source file combines environment access, network access, and code or shell execution; review context before blocking.

dist/bin/jpi.jsView on unpkg · L5
dist/bin/jpi-refresh.jsView file
60const raw = readFileSync3(CONFIG_FILE, "utf8"); L61: const parsed = JSON.parse(raw); L62: return { ...DEFAULT_CONFIG, ...parsed }; ... L82: function getNudgeMode() { L83: const envVal = process.env["TERMINALHIRE_NUDGE"]; L84: if (envVal) { ... L222: { id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] }, L223: { id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] }, L224: { id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] }, ... L314: { id: "microservices" }, L315: { id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] }, L316: { id: "realtime", synonyms: ["real-time"] },
High
Host Fingerprint Exfiltration

Source collects local host identity data and sends it to an external endpoint.

dist/bin/jpi-refresh.jsView on unpkg · L60
dist/bin/jpi-sync.jsView file
128{ id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] }, L129: { id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] }, L130: { id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] }, ... L220: { id: "microservices" }, L221: { id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] }, L222: { id: "realtime", synonyms: ["real-time"] }, ... L843: "use strict"; L844: KDF_INFO = Buffer.from("terminalhire-chat-v1"); L845: } ... L1209: init_src(); L1210: TERMINALHIRE_DIR = join2(homedir(), ".terminalhire"); L1211: PROFILE_FILE = join2(TERMINALHIRE_DIR, "profile.enc");
High
Sandbox Evasion Gated Capability

Source gates dangerous network, credential, or execution behavior behind CI, host, platform, time, or geo fingerprint checks.

dist/bin/jpi-sync.jsView on unpkg · L128
dist/keytar-KOAAH267.nodeView file
path = dist/keytar-KOAAH267.node kind = native_binary sizeBytes = 99528 magicHex = [redacted]
Medium
Ships Native Binary

Package ships native binary artifacts.

dist/keytar-KOAAH267.nodeView on unpkg

Findings

1 Critical7 High6 Medium7 Low
CriticalPrevious Version Dangerous Deltadist/bin/jpi-dispatch.js
HighInstall Time Lifecycle Scriptspackage.json
HighChild Processdist/bin/jpi-chat.js
HighShelldist/bin/jpi-statusline-launch.js
HighSame File Env Network Executiondist/bin/jpi.js
HighHost Fingerprint Exfiltrationdist/bin/jpi-refresh.js
HighSandbox Evasion Gated Capabilitydist/bin/jpi-sync.js
HighCross File Remote Execution Contextdist/bin/jpi-chat.js
MediumAmbiguous Install Lifecycle Scriptpackage.json
MediumDynamic Requiredist/bin/jpi-init.js
MediumNetwork
MediumEnvironment Vars
MediumShips Native Binarydist/keytar-KOAAH267.node
MediumStructural Risk Force Deep Review
LowNon Install Lifecycle Scripts
LowScripts Present
LowEvaldist/bin/jpi-dispatch.js
LowFilesystem
LowObfuscated
LowHigh Entropy Strings
LowUrl Strings