High risk. Don't ship without significant remediation.
Scanned 6/13/2026, 7:14:17 PM·Cached result·Deep Scan·91 rules·View source ↗·How we decide ↗
AIVSS Score
High
Severity Breakdown
0
critical
18
high
139
medium
76
low
MCP Server Information
Findings
This package receives a D security grade with a safety score of 46/100, driven primarily by 18 high-severity issues including 9 command injection vulnerabilities, 13 ANSI escape injection flaws, and 50 resource exhaustion risks. The 139 medium-severity findings span server configuration problems, readiness concerns, and potential prompt injection vectors that could allow attackers to manipulate the package's behavior or exhaust system resources. Installation is not recommended without significant remediation of the high-severity command injection and resource exhaustion vulnerabilities.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 — 38 of 38 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasn’t shipped yet.
Showing 1–30 of 38 findings
38 findings
Tool 'before-prompt-recall' performs undisclosed NETWORK side effect: POSTs to /internal/prompt-recall endpoint via HTTP request, not mentioned in description.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * before-prompt-recall — Claude Code UserPromptSubmit hook. |
| 4 | * |
RemediationAI
The problem is that before-prompt-recall.js performs an undisclosed HTTP POST to /internal/prompt-recall without documenting this network side effect in the tool's description or metadata. Add a `sideEffects` field to the tool's MCP resource definition (or update the description comment) to explicitly declare: `"sideEffects": ["network: POST to /internal/prompt-recall"]`. This transparency ensures operators and auditors know the tool makes external network calls. Verify by checking that the tool's JSON schema or documentation now lists the network side effect before deployment.
Tool 'before-prompt-recall' fetches and executes remote code: handler POSTs to /internal/prompt-recall endpoint, receives contextText response, and echoes it to stdout for injection into Claude Code's context without validation of source or content.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * before-prompt-recall — Claude Code UserPromptSubmit hook. |
| 4 | * |
| 5 | * Per OpenClaw import T2.2: a bounded pre-reply memory recall pass. Reads the |
| 6 | * user's prompt from stdin, POSTs to instar's /internal/prompt-recall, and |
| 7 | * echoes the resulting context block to stdout (which Claude Code injects |
| 8 | * as additional context for the upcoming turn). |
| 9 | * |
| 10 | * The hook is synchronous from Claude Code's perspective — Claude Code waits |
| 11 | * for stdout before continuing. The server's Prompt |
RemediationAI
The problem is that before-prompt-recall.js receives arbitrary `contextText` from the remote /internal/prompt-recall endpoint and echoes it directly to stdout without validating the source, integrity, or content, allowing injection of malicious context into Claude's reasoning. Wrap the received `contextText` in a provenance header (e.g., `<!-- RECALL_SOURCE: /internal/prompt-recall --> ${contextText}`) and add a Content-Security-Policy-like validation: check the response's `X-Content-Hash` header against a pinned allowlist or require a cryptographic signature before echoing. Verify by testing that unsigned or tampered responses are rejected and logged.
Tool 'before-prompt-recall' fetches a context block from /internal/prompt-recall endpoint per-call and injects it as additional LLM context, steering the model's behavior based on remote-fetched recall data.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * before-prompt-recall — Claude Code UserPromptSubmit hook. |
| 4 | * |
RemediationAI
The problem is that before-prompt-recall.js fetches remote context on every call and injects it into the LLM's reasoning without the operator's explicit per-request consent, allowing the remote server to steer model behavior. Disable automatic context injection by default and require an explicit `--enable-recall` flag or environment variable (`INSTAR_RECALL_ENABLED=true`) that the operator must set before the tool activates. Verify by confirming that without the flag set, the tool exits cleanly without making the POST request.
Unsafe deserialization primitive detected. pickle.load(s), yaml.load (without SafeLoader), marshal.load(s), and shelve.open execute arbitrary code when the input is attacker-controlled.
Evidence
| 98 | throw new Error(`${filePath}: missing YAML frontmatter delimited by --- on lines 1 and N`); |
| 99 | } |
| 100 | const [, frontmatterRaw, body] = match; |
| 101 | const frontmatter = yaml.load(frontmatterRaw, { schema: yaml.FAILSAFE_SCHEMA }); |
| 102 | if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter)) { |
| 103 | throw new Error(`${filePath}: frontmatter must parse to an object`); |
| 104 | } |
RemediationAI
The problem is that the code uses `yaml.load(frontmatterRaw, { schema: yaml.FAILSAFE_SCHEMA })` which, while safer than the default, can still deserialize arbitrary JavaScript objects in some YAML parsers. Replace `yaml.load()` with `yaml.load(..., { schema: yaml.JSON_SCHEMA })` or use a strict parsing library like `js-yaml` with `{ schema: 'safe' }` to ensure only primitive types and plain objects are deserialized. Verify by attempting to inject a YAML constructor payload (e.g., `!!js/object:Function`) and confirming it is rejected or parsed as a string.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 47 | } catch { |
| 48 | base = execSync('git rev-parse HEAD~1', { encoding: 'utf-8' }).trim(); |
| 49 | } |
| 50 | const out = execSync(`git diff --name-only ${base} HEAD`, { encoding: 'utf-8' }); |
| 51 | changed = out.split('\n').map(s => s.trim()).filter(Boolean); |
| 52 | } catch (err) { |
| 53 | console.warn(`pre-push-e2e-scope: could not compute changed files (${err instanceof Error ? err.message : err}) — skipping (CI still runs e2e).`); |
RemediationAI
The problem is that `execSync('git diff --name-only ${base} HEAD', ...)` interpolates the `base` variable directly into a shell command string, allowing an attacker who controls `base` to inject arbitrary shell commands. Replace the template string with an array of arguments: `execSync(['git', 'diff', '--name-only', base, 'HEAD'], { encoding: 'utf-8' })` (using Node's `spawnSync` or a wrapper that supports list-form arguments). Verify by setting `base` to a value like `HEAD; rm -rf /` and confirming the command fails or is safely escaped.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 182 | execSync(`curl -L -f -o "${tmpFile}" "${url}"`, { stdio: 'pipe', timeout: 30000 }); |
| 183 | |
| 184 | if (fs.existsSync(buildDir)) fs.rmSync(buildDir, { recursive: true }); |
| 185 | execSync(`tar xzf "${tmpFile}" -C "${pkgDir}"`, { stdio: 'pipe' }); |
| 186 | return true; |
| 187 | } catch (err) { |
| 188 | console.warn(`[fix-better-sqlite3] prebuild download/extract failed: ${err.message}`); |
RemediationAI
The problem is that `execSync('curl -L -f -o "${tmpFile}" "${url}"', ...)` and `execSync('tar xzf "${tmpFile}" -C "${pkgDir}"', ...)` interpolate `tmpFile`, `url`, and `pkgDir` into shell strings, allowing command injection if these variables are attacker-controlled. Replace both calls with array-form commands: `execSync(['curl', '-L', '-f', '-o', tmpFile, url], ...)` and `execSync(['tar', 'xzf', tmpFile, '-C', pkgDir], ...)` using `spawnSync` or a library that supports list arguments. Verify by injecting shell metacharacters (e.g., `; echo pwned`) into `url` and confirming the command fails safely.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 157 | // PR's own changes (merge-base of main and HEAD is the branch point). |
| 158 | const pickRef = (cands) => { |
| 159 | for (const r of cands) { |
| 160 | try { execSync(`git rev-parse --verify --quiet ${r}`, { stdio: 'pipe', encoding: 'utf-8' }); return r; } |
| 161 | catch { /* ref not present in this clone */ } |
| 162 | } |
| 163 | return null; |
RemediationAI
The problem is that `execSync('git rev-parse --verify --quiet ${r}', ...)` interpolates the ref variable `r` directly into a shell command, allowing injection if `r` contains shell metacharacters. Replace with array-form: `execSync(['git', 'rev-parse', '--verify', '--quiet', r], { stdio: 'pipe', encoding: 'utf-8' })` using `spawnSync` or a wrapper supporting list arguments. Verify by testing with a ref value like `HEAD; echo pwned` and confirming it is safely escaped or rejected.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 179 | try { |
| 180 | console.log(`[fix-better-sqlite3] Downloading ${url}`); |
| 181 | execSync(`curl -L -f -o "${tmpFile}" "${url}"`, { stdio: 'pipe', timeout: 30000 }); |
| 182 | |
| 183 | if (fs.existsSync(buildDir)) fs.rmSync(buildDir, { recursive: true }); |
| 184 | execSync(`tar xzf "${tmpFile}" -C "${pkgDir}"`, { stdio: 'pipe' }); |
RemediationAI
The problem is that both `curl` and `tar` commands interpolate `tmpFile`, `url`, and `pkgDir` into shell strings without proper escaping, enabling command injection. Refactor to use array-form `spawnSync`: `spawnSync('curl', ['-L', '-f', '-o', tmpFile, url], { stdio: 'pipe', timeout: 30000 })` and `spawnSync('tar', ['xzf', tmpFile, '-C', pkgDir], { stdio: 'pipe' })`. Verify by injecting shell metacharacters into `url` or `pkgDir` and confirming safe handling.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 126 | function getStagedContent(filepath) { |
| 127 | try { |
| 128 | return execSync(`git show :"${filepath}"`, { encoding: 'utf-8' }); |
| 129 | } catch { |
| 130 | // File staged for deletion or unreadable — skip. |
| 131 | return null; |
RemediationAI
The problem is that `execSync('git show :"${filepath}"', ...)` interpolates the filepath directly into a shell command, allowing injection if the filepath contains shell metacharacters or quotes. Replace with array-form: `execSync(['git', 'show', `:${filepath}`], { encoding: 'utf-8' })` using `spawnSync` or a wrapper supporting list arguments. Verify by testing with a filepath like `file"; echo pwned #` and confirming safe escaping.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 69 | const { entryPath, entryData } = pendingAuditEntry; |
| 70 | entryData.verdict = code === 0 ? 'pass' : 'blocked'; |
| 71 | fs.writeFileSync(entryPath, JSON.stringify(entryData, null, 2) + '\n'); |
| 72 | execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT }); |
| 73 | } catch { /* best-effort — 'pending' is still more truthful than no verdict */ } |
| 74 | }); |
| 75 | const WINDOW_MS = 60 * 60 * 1000; // 60 minutes |
RemediationAI
The problem is that `execSync('git add ${JSON.stringify(path.relative(ROOT, entryPath))}', ...)` relies on `JSON.stringify()` for escaping, which is insufficient for shell contexts and can be bypassed with certain payloads. Replace with array-form: `execSync(['git', 'add', path.relative(ROOT, entryPath)], { cwd: ROOT })` using `spawnSync` or a wrapper supporting list arguments. Verify by testing with a path containing shell metacharacters and confirming safe handling.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 1048 | verdict: 'pending', |
| 1049 | }; |
| 1050 | fs.writeFileSync(entryPath, JSON.stringify(entryData, null, 2) + '\n'); |
| 1051 | execSync(`git add ${JSON.stringify(path.relative(ROOT, entryPath))}`, { cwd: ROOT }); |
| 1052 | pendingAuditEntry = { entryPath, entryData }; |
| 1053 | return entryPath; |
| 1054 | } catch { |
RemediationAI
The problem is that `execSync('git add ${JSON.stringify(path.relative(ROOT, entryPath))}', ...)` uses `JSON.stringify()` for shell escaping, which is not shell-safe and can be bypassed. Replace with array-form: `execSync(['git', 'add', path.relative(ROOT, entryPath)], { cwd: ROOT })` using `spawnSync` or a wrapper supporting list arguments. Verify by injecting shell metacharacters into the path and confirming safe handling.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 164 | }; |
| 165 | const remoteBranch = pickRef(['JKHeadley/main', 'origin/main', 'upstream/main', 'main']) |
| 166 | || execSync('git rev-parse --abbrev-ref @{u} 2>/dev/null || echo origin/main', { encoding: 'utf-8' }).trim(); |
| 167 | const changedFiles = execSync(`git diff --name-only ${remoteBranch}...HEAD 2>/dev/null || git diff --name-only HEAD~1 2>/dev/null`, { encoding: 'utf-8' }) |
| 168 | .trim() |
| 169 | .split('\n') |
| 170 | .filter(Boolean); |
RemediationAI
The problem is that `execSync('git diff --name-only ${remoteBranch}...HEAD 2>/dev/null || ...')` interpolates `remoteBranch` directly into a shell command, allowing injection. Replace with array-form: `execSync(['git', 'diff', '--name-only', `${remoteBranch}...HEAD`], { encoding: 'utf-8' })` using `spawnSync` or a wrapper supporting list arguments. Verify by setting `remoteBranch` to a value like `origin/main; echo pwned` and confirming safe escaping.
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 44 | function stagedContent(file) { |
| 45 | try { |
| 46 | return execSync(`git show :${file}`, { encoding: 'utf-8' }); |
| 47 | } catch { |
| 48 | return ''; |
| 49 | } |
RemediationAI
The problem is that `execSync('git show :${file}', ...)` interpolates the file variable directly into a shell command without proper escaping, enabling injection. Replace with array-form: `execSync(['git', 'show', `:${file}`], { encoding: 'utf-8' })` using `spawnSync` or a wrapper supporting list arguments. Verify by testing with a filename like `file"; echo pwned #` and confirming safe handling.
before-prompt-recall.js reads the user prompt from stdin and POSTs it to an external server endpoint (INSTAR_SERVER_URL) with Bearer token authentication, transmitting potentially sensitive user input to a destination controlled by environment variables rather than the operator.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * before-prompt-recall — Claude Code UserPromptSubmit hook. |
| 4 | * |
| 5 | * Per OpenClaw import T2.2: a bounded pre-reply memory recall pass. Reads the |
| 6 | * user's prompt from stdin, POSTs to instar's /internal/prompt-recall, and |
| 7 | * echoes the resulting context block to stdout (which Claude Code injects |
| 8 | * as additional context for the upcoming turn). |
| 9 | * |
| 10 | * The hook is synchronous from Claude Code's perspective — Claude Code waits |
| 11 | * for stdout before continuing. The server's Prompt |
RemediationAI
The problem is that before-prompt-recall.js reads sensitive user prompts from stdin and POSTs them to an external server (INSTAR_SERVER_URL) controlled by environment variables, without the operator's explicit per-request consent or knowledge of the destination. Add a configuration file (e.g., `~/.instar/config.json`) that the operator must explicitly create and approve, containing the allowed server URL and authentication token, rather than relying on environment variables alone. Verify by confirming that the tool fails with a clear error if the config file is missing or if the destination URL differs from the approved value.
Tool 'before-prompt-recall' uses module-level global authToken from process.env.INSTAR_AUTH_TOKEN to POST to /internal/prompt-recall without consulting per-request caller identity.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * before-prompt-recall — Claude Code UserPromptSubmit hook. |
| 4 | * |
RemediationAI
The problem is that before-prompt-recall.js uses a global `process.env.INSTAR_AUTH_TOKEN` to authenticate all requests to /internal/prompt-recall without verifying the caller's identity or checking if the token is still valid. Replace the global token with a per-request identity check: require the caller to pass a request-scoped credential or session token (e.g., via stdin or a callback) and validate it before making the POST. Verify by confirming that requests without a valid per-request credential are rejected.
before-prompt-recall hook fetches untrusted contextText from /internal/prompt-recall endpoint and returns it verbatim to stdout (injected into Claude's context) with no provenance delimiter or source attribution wrapper.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * before-prompt-recall — Claude Code UserPromptSubmit hook. |
| 4 | * |
RemediationAI
The problem is that before-prompt-recall.js returns the `contextText` from /internal/prompt-recall verbatim to stdout with no provenance marker, source attribution, or delimiter, making it indistinguishable from the user's original prompt. Wrap the returned context in a clearly marked block: `<!-- INSTAR_RECALL_START --> ${contextText} <!-- INSTAR_RECALL_END -->` so Claude and the operator can identify injected content. Verify by checking that the output now includes the provenance delimiters and that Claude's context window shows the source attribution.
A variable named like a secret (secret/token/apikey/password/ credential/private_key/bearer) is emitted to a logger, stdout, HTTP response, MCP tool response, or file write without redaction. If the value is genuinely not sensitive, rename it; otherwise wrap with a redaction helper.
Evidence
| 125 | // Save private key to a known transitional location for the user to register |
| 126 | // via `instar worktree register-keypair --private <path>` (which prompts for keychain). |
| 127 | const privPath = path.join(STATE_DIR, 'trailer-private-key.pem.NEW'); |
| 128 | fs.writeFileSync(privPath, privateKey, { mode: 0o600 }); |
| 129 | logStep(`Private key written to ${privPath} (chmod 0600)`); |
| 130 | logStep(' → Next: run `instar worktree register-keypair --private ' + privPath + '` to move into keychain.'); |
| 131 | logStep(' → After succ |
RemediationAI
The problem is that the variable `privateKey` is written to a file and the path is printed to stdout/logs without redaction, potentially exposing the secret's location or value if logs are captured. Rename the variable to `privateKeyPath` (if it contains only the path) or wrap the output with a redaction helper: `console.log(`Private key saved to: ${redact(privPath)}`)` where `redact()` masks sensitive paths. Verify by checking logs and stdout to confirm the path is masked or that the variable name no longer suggests it contains a secret.
XML parser configured without entity expansion disabled. XXE (XML external entity) attacks can read local files, exfiltrate data, and cause SSRF when the parser resolves external entities.
Evidence
| 117 | if command -v python3 &>/dev/null; then |
| 118 | python3 - "$plist" 2>/dev/null <<'PYEOF' || true |
| 119 | import sys, xml.etree.ElementTree as ET |
| 120 | d = ET.parse(sys.argv[1]).getroot().find('dict') |
| 121 | els = list(d) |
| 122 | for i, el in enumerate(els): |
| 123 | if el.tag == 'key' and el.text == 'WorkingDirectory' and i + 1 < len(els): |
RemediationAI
The problem is that the Python XML parser `xml.etree.ElementTree.parse()` is used without disabling external entity expansion, allowing XXE attacks to read local files or cause SSRF. Replace the parser call with: `parser = ET.XMLParser(); parser.entity = {}; ET.parse(sys.argv[1], parser=parser).getroot()` or use `defusedxml.ElementTree.parse()` which disables entity expansion by default. Verify by attempting to inject an XXE payload (e.g., `<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>`) and confirming it is rejected or safely handled.
Hardcoded secret detected in source. MCP servers often proxy between the model and a third-party API, so any committed credential grants that access to anyone who can read the repo. Move to an environment variable or secret manager and rotate the leaked value.
Evidence
| 301 | Restart Claude Code, then run a command that would expose a credential: |
| 302 | |
| 303 | ```bash |
| 304 | echo "sk-ant-api03-test1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrst" |
| 305 | ``` |
| 306 | |
| 307 | The hook should block the response and show a masked version of the detected key. |
RemediationAI
The problem is that the SKILL.md file contains a hardcoded example API key (`sk-ant-api03-test1234567890...`) in plaintext, which if committed to a public repository grants anyone access to the Anthropic API. Remove the hardcoded key from the file and replace it with a placeholder: `sk-ant-api03-[REDACTED]` or `sk-ant-api03-YOUR_KEY_HERE`. Verify by running `git log -p` to confirm the old key is not in history, and rotate the actual API key in the Anthropic console.
Hardcoded secret detected in source. MCP servers often proxy between the model and a third-party API, so any committed credential grants that access to anyone who can read the repo. Move to an environment variable or secret manager and rotate the leaked value.
Evidence
| 24 | |---------|--------------| |
| 25 | | OpenAI API keys | `sk-proj-abc123...` | |
| 26 | | Anthropic API keys | `sk-ant-api03-...` | |
| 27 | | AWS access keys | `AKIA1234567890ABCDEF` | |
| 28 | | GitHub tokens (classic) | `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | |
| 29 | | GitHub fine-grained PATs | `github_pat_xxxxxx...` | |
| 30 | | Stripe secret keys | `sk_live_xxxx...xxxx` | |
RemediationAI
The problem is that the SKILL.md file contains hardcoded example API key patterns (OpenAI, Anthropic, AWS, GitHub, Stripe) in plaintext, which could be mistaken for real credentials or used as templates for attacks. Replace all example keys with clearly marked placeholders: `sk-proj-[REDACTED]`, `AKIA[REDACTED]`, `ghp_[REDACTED]`, etc., and add a comment: `<!-- Example keys are redacted; use your own credentials -->`. Verify by confirming that no real-looking credentials remain in the file and that the examples are clearly marked as placeholders.
Hardcoded secret detected in source. MCP servers often proxy between the model and a third-party API, so any committed credential grants that access to anyone who can read the repo. Move to an environment variable or secret manager and rotate the leaked value.
Evidence
| 25 | | OpenAI API keys | `sk-proj-abc123...` | |
| 26 | | Anthropic API keys | `sk-ant-api03-...` | |
| 27 | | AWS access keys | `AKIA1234567890ABCDEF` | |
| 28 | | GitHub tokens (classic) | `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | |
| 29 | | GitHub fine-grained PATs | `github_pat_xxxxxx...` | |
| 30 | | Stripe secret keys | `sk_live_xxxx...xxxx` | |
| 31 | | PEM private keys | `-----BEGIN RSA PRIVATE KEY-----` | |
RemediationAI
The problem is that the SKILL.md file contains hardcoded example API key patterns (OpenAI, Anthropic, AWS, GitHub, Stripe) in plaintext, which could be mistaken for real credentials or used as templates for attacks. Replace all example keys with clearly marked placeholders: `sk-proj-[REDACTED]`, `AKIA[REDACTED]`, `ghp_[REDACTED]`, etc., and add a comment: `<!-- Example keys are redacted; use your own credentials -->`. Verify by confirming that no real-looking credentials remain in the file and that the examples are clearly marked as placeholders.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 110 | sys.exit(1) |
| 111 | content = sys.stdin.buffer.read() |
| 112 | checksum = atomic_write(sys.argv[2], content) |
| 113 | print(f"Written: {sys.argv[2]} (sha256:{checksum})") |
| 114 | |
| 115 | elif cmd == "verify": |
| 116 | if len(sys.argv) < 3: |
RemediationAI
The problem is that `print(f"Written: {sys.argv[2]} (sha256:{checksum})")` outputs user-controlled file paths without ANSI escape sanitization, allowing injection of cursor-control sequences that can rewrite terminal output or hide commands. Sanitize the output by stripping ANSI escape sequences: `import re; safe_path = re.sub(r'\x1b\[[0-9;]*m', '', sys.argv[2]); print(f"Written: {safe_path} (sha256:{checksum})")`. Verify by passing a filename with ANSI codes (e.g., `\x1b[2J`) and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 92 | const target = path.join(TEMPLATES_DIR, `${job.slug}.md`); |
| 93 | fs.writeFileSync(target, renderTemplate(job), 'utf-8'); |
| 94 | written.add(`${job.slug}.md`); |
| 95 | console.log(` wrote ${job.slug}.md (${job.execute.value.length} body bytes)`); |
| 96 | } |
| 97 | |
| 98 | // Prune templates that no longer correspond to a default. The spec moves |
RemediationAI
The problem is that `console.log(' wrote ${job.slug}.md (${job.execute.value.length} body bytes)')` outputs user-controlled job slugs without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `const safeSlug = job.slug.replace(/\x1b\[[0-9;]*m/g, ''); console.log(' wrote ${safeSlug}.md (${job.execute.value.length} body bytes)')`. Verify by passing a slug with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 109 | import sys |
| 110 | from urllib.parse import urlparse |
| 111 | try: |
| 112 | print((urlparse(sys.argv[1]).hostname or '').lower()) |
| 113 | except Exception: |
| 114 | print('') |
| 115 | PY |
RemediationAI
The problem is that `print((urlparse(sys.argv[1]).hostname or '').lower())` outputs user-controlled hostnames without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `import re; safe_host = re.sub(r'\x1b\[[0-9;]*m', '', (urlparse(sys.argv[1]).hostname or '').lower()); print(safe_host)`. Verify by passing a URL with ANSI codes in the hostname and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 163 | if len(sys.argv) < 3: |
| 164 | print("Usage: playbook-hmac.py sign <file>", file=sys.stderr) |
| 165 | sys.exit(1) |
| 166 | print(sign_file(sys.argv[2])) |
| 167 | |
| 168 | elif cmd == "verify": |
| 169 | if len(sys.argv) < 4: |
RemediationAI
The problem is that `print(sign_file(sys.argv[2]))` outputs the result of signing a user-controlled file without ANSI escape sanitization, allowing injection of cursor-control sequences in the output. Sanitize the output: `import re; safe_sig = re.sub(r'\x1b\[[0-9;]*m', '', sign_file(sys.argv[2])); print(safe_sig)`. Verify by passing a file with ANSI codes in its content and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 294 | for (const reply of received) { |
| 295 | if (reply) { |
| 296 | const name = [echo, dan, dude].find(a => a.agentId === reply.from)?.name || 'unknown'; |
| 297 | console.log(` 💬 ${name}: "${decodePayload(reply.payload).substring(0, 70)}..."`); |
| 298 | } |
| 299 | } |
| 300 | } catch (e) { |
RemediationAI
The problem is that `console.log(' 💬 ${name}: "${decodePayload(reply.payload).substring(0, 70)}..."')` outputs user-controlled message content without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `const safeMsg = decodePayload(reply.payload).substring(0, 70).replace(/\x1b\[[0-9;]*m/g, ''); console.log(' 💬 ${name}: "${safeMsg}..."')`. Verify by passing a message with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 100 | if (out.tier === 1) { |
| 101 | if (!out.sideEffectsPath) out.sideEffectsPath = out.artifact; |
| 102 | if (!out.eli16Path) { |
| 103 | console.error('A Tier-1 trace requires --eli16-path (the request ELI16 overview).'); |
| 104 | process.exit(1); |
| 105 | } |
| 106 | } |
RemediationAI
The problem is that `console.error('A Tier-1 trace requires --eli16-path (the request ELI16 overview).')` outputs user-controlled error messages without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `const safeMsg = 'A Tier-1 trace requires --eli16-path (the request ELI16 overview).'.replace(/\x1b\[[0-9;]*m/g, ''); console.error(safeMsg)`. Verify by passing input with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 276 | idx = sys.argv.index("--source-action") |
| 277 | kwargs["source_action"] = sys.argv[idx + 1] |
| 278 | data = add_strategy(sys.argv[2], sys.argv[3], sys.argv[4], **kwargs) |
| 279 | print(f"Added strategy in {sys.argv[3]} domain. Total: {len(data['strategies_discovered'])}") |
| 280 | |
| 281 | elif cmd == "add-failure": |
| 282 | if len(sys.argv) < 5: |
RemediationAI
The problem is that `print(f"Added strategy in {sys.argv[3]} domain. Total: {len(data['strategies_discovered'])}")` outputs user-controlled domain names without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `import re; safe_domain = re.sub(r'\x1b\[[0-9;]*m', '', sys.argv[3]); print(f"Added strategy in {safe_domain} domain. Total: {len(data['strategies_discovered'])}")`. Verify by passing a domain with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 154 | print(f"# X-Markdown-Tokens: {result['token_hint']}") |
| 155 | print(f"# Estimated tokens: {tokens}") |
| 156 | print("---") |
| 157 | print(result['body']) |
| 158 | |
| 159 | if tokens > max_tokens: |
| 160 | log(f"[smart-fetch] WARNING: Content exceeds {max_tokens} token limit") |
RemediationAI
The problem is that `print(result['body'])` outputs user-controlled content without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `import re; safe_body = re.sub(r'\x1b\[[0-9;]*m', '', result['body']); print(safe_body)`. Verify by passing content with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 179 | if len(sys.argv) < 4: |
| 180 | print("Usage: playbook-hmac.py chain-sign <json_str> <prev_hmac>", file=sys.stderr) |
| 181 | sys.exit(1) |
| 182 | print(chain_sign(sys.argv[2], sys.argv[3])) |
| 183 | |
| 184 | elif cmd == "chain-verify": |
| 185 | if len(sys.argv) < 5: |
RemediationAI
The problem is that `print(chain_sign(sys.argv[2], sys.argv[3]))` outputs the result of a chain-sign operation without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `import re; safe_sig = re.sub(r'\x1b\[[0-9;]*m', '', chain_sign(sys.argv[2], sys.argv[3])); print(safe_sig)`. Verify by passing input with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 238 | if cmd == "append": |
| 239 | if len(sys.argv) < 5: |
| 240 | print("Usage: playbook-history.py append <operation> <item_id> <source_session> [--payload '{}']", file=sys.stderr) |
| 241 | sys.exit(1) |
| 242 | operation = sys.argv[2] |
| 243 | item_id = sys.argv[3] |
RemediationAI
The problem is that `print(summary(sys.argv[2]))` outputs user-controlled summary data without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `import re; safe_summary = re.sub(r'\x1b\[[0-9;]*m', '', summary(sys.argv[2])); print(safe_summary)`. Verify by passing a session ID with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 335 | if len(sys.argv) < 3: |
| 336 | print("Usage: playbook-scratchpad.py summary <session_id>", file=sys.stderr) |
| 337 | sys.exit(1) |
| 338 | print(summary(sys.argv[2])) |
| 339 | |
| 340 | else: |
| 341 | print(f"Unknown command: {cmd}", file=sys.stderr) |
RemediationAI
The problem is that `print(f"Unknown command: {cmd}", file=sys.stderr)` outputs user-controlled command names without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output: `import re; safe_cmd = re.sub(r'\x1b\[[0-9;]*m', '', cmd); print(f"Unknown command: {safe_cmd}", file=sys.stderr)`. Verify by passing a command with ANSI codes and confirming the output is sanitized.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 8 | * Hardened replacement for the ad-hoc `curl + jq` / `curl + python` pattern |
| 9 | * that historically leaked credentials in plaintext into the Bash tool |
| 10 | * transcript. The lesson: when probing an unknown JSON shape, NEVER fall |
| 11 | * back to `console.log(JSON.stringify(body))` — that's how plaintext |
| 12 | * credentials end up in shell history, session transcripts, and downstream |
| 13 | * LLM context. |
| 14 | * |
RemediationAI
The problem is that the comment references a pattern of logging plaintext credentials via `console.log(JSON.stringify(body))`, which outputs user-controlled JSON without ANSI escape sanitization or credential redaction, allowing injection of cursor-control sequences and exposure of secrets. Replace any such logging with a sanitized, redacted version: `const safe = JSON.stringify(body).replace(/\x1b\[[0-9;]*m/g, '').replace(/"(password|token|key)":\s*"[^"]*"/g, '"$1": "[REDACTED]"'); console.log(safe)`. Verify by passing JSON with ANSI codes and credentials, and confirming both are sanitized.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 173 | "stale": len(results["stale"]), |
| 174 | "skipped": len(results["skipped"]), |
| 175 | } |
| 176 | atomic_append(VERIFY_LOG, json.dumps(log_entry, separators=(",", ":"))) |
| 177 | except Exception: |
| 178 | pass |
| 179 | |
| 180 | return { |
| 181 | "status": "complete", |
RemediationAI
The problem is that the `except Exception: pass` block silently swallows all exceptions from the atomic_append call, hiding failures and preventing incident response. Replace with: `except Exception as e: logging.error(f"Failed to append verify log: {e}", exc_info=True)` and ensure a logger is configured to write to a file or monitoring system. Verify by triggering an exception (e.g., by making the log file read-only) and confirming the error is logged.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 103 | for line in AUDIT_LOG.read_text().splitlines(): |
| 104 | if line.strip(): |
| 105 | try: |
| 106 | entries.append(json.loads(line)) |
| 107 | except json.JSONDecodeError: |
| 108 | pass |
| 109 | return entries |
RemediationAI
The problem is that the `except json.JSONDecodeError: pass` block silently discards malformed JSON lines, hiding data corruption or parsing failures. Replace with: `except json.JSONDecodeError as e: logging.warning(f"Skipping malformed JSON line: {line[:50]}... ({e})")` and ensure a logger is configured. Verify by adding a malformed JSON line to the audit log and confirming the warning is logged.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 20 | Per the 2026-05-29 pipeline post-mortem (PR #545), pattern #4 was |
| 21 | "silent failure caught only by user." The worst recent instance was the |
| 22 | **PromptGate $452 incident** — a bare `catch {}` in a 5-second hot-path |
| 23 | detection loop that swallowed every rate-limit failure for hours, |
| 24 | bypassing both QuotaTracker and LlmQueue spend guards. By the time it |
| 25 | surfaced, it had burned $452 of credits. |
RemediationAI
The problem is that the documentation references a bare `catch {}` in a hot-path detection loop that swallowed rate-limit failures for hours, causing the PromptGate $452 incident. Replace all bare `catch {}` blocks with: `catch (err) { logger.error('Caught exception in hot path:', err); metrics.increment('errors.hot_path'); }` and ensure errors are logged and metricated. Verify by triggering an exception in the hot path and confirming it is logged and metricated.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 319 | ["python3", os.path.join(SCRIPT_DIR, "telegram-reply.py"), "285"], |
| 320 | input=message, text=True, timeout=10, |
| 321 | capture_output=True, |
| 322 | ) |
| 323 | except Exception: |
| 324 | pass # Non-critical — review items are persisted regardless |
| 325 | |
| 326 | |
| 327 | def _resolve_conflicts(deltas, manifest): |
RemediationAI
The problem is that the `except Exception: pass` block silently swallows exceptions from the telegram-reply subprocess call, hiding failures in non-critical notification logic. Replace with: `except Exception as e: logging.warning(f"Failed to send telegram reply: {e}")` and ensure a logger is configured. Verify by making the telegram-reply script fail and confirming the warning is logged.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 165 | history = _import_history() |
| 166 | history.append_entry( |
| 167 | "resurrect", item_id, session_id, |
| 168 | payload={"reason": reason}) |
| 169 | except Exception: |
| 170 | pass |
| 171 | |
| 172 | log_entry = { |
| 173 | "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), |
RemediationAI
The problem is that the `except Exception: pass` block silently swallows exceptions from the history.append_entry call, hiding failures in the resurrection logic. Replace with: `except Exception as e: logging.error(f"Failed to resurrect item {item_id}: {e}", exc_info=True)` and ensure a logger is configured. Verify by triggering an exception (e.g., by making the history file read-only) and confirming the error is logged.