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 carries a C-grade security rating with 2 high-severity issues and 77 medium-severity findings, primarily concentrated in resource exhaustion vulnerabilities (50 cases) and readiness concerns (76 cases). The presence of 3 hardcoded secrets, potential insecure deserialization, XXE exposure, and 13 ansi escape injection vulnerabilities indicates multiple attack vectors that could be exploited. Installation is not recommended without significant remediation efforts and a thorough security review of how this package handles untrusted input and resource management.
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 manifest (e.g., in tool.json or the tool definition) explicitly listing 'network: POST /internal/prompt-recall' and update the description to state 'This tool makes an outbound HTTP request to fetch recall context.' This ensures callers and auditors can see the network behavior upfront. Verify by checking that `mcp inspect` or your tool registry now displays the side effect in the tool's metadata.
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 /internal/prompt-recall endpoint and echoes it directly to stdout without validation, allowing injection of malicious content into Claude's context. Wrap the received contextText in a clearly marked provenance block (e.g., `<!-- RECALL SOURCE: /internal/prompt-recall -->[content]<!-- END RECALL -->`) and validate that the response is valid JSON with expected schema before echoing. This makes the injected content's origin transparent and prevents unmarked context injection. Test by sending a malformed or hostile response from the endpoint and verify it is either rejected or clearly marked as external.
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 user's explicit awareness, allowing remote steering of model behavior. Require explicit user opt-in via a command-line flag or config file (e.g., `--enable-recall` or `INSTAR_RECALL_ENABLED=true`) and log each recall fetch with source and timestamp to stderr. This ensures the user controls whether remote context injection is active. Verify by checking that the tool refuses to fetch recall data unless the flag is set, and that each fetch is logged.
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 is safe, but the finding suggests unsafe deserialization elsewhere in the codebase (Python pickle/marshal/shelve). Audit all YAML parsing to ensure `yaml.FAILSAFE_SCHEMA` is used consistently, and replace any `yaml.load()` without an explicit safe schema with `yaml.safe_load()`. For Python code, replace `pickle.load()` with `json.load()`, `yaml.load()` with `yaml.safe_load()`, and remove `shelve.open()` calls; if binary serialization is required, use `msgpack` with `strict_map_key=False`. Test by attempting to deserialize a payload containing Python object constructors and confirming it raises an error rather than executing code.
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 command injection if `base` is attacker-controlled. Replace the template string with an array of arguments: `execSync(['git', 'diff', '--name-only', base, 'HEAD'], { encoding: 'utf-8' })` (or use `spawnSync` with `shell: false`). This prevents shell interpretation of special characters in `base`. Verify by setting `base` to a value like `HEAD; rm -rf /` and confirming the command fails or treats it as a literal ref name.
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, risking command injection. Replace both with array-based calls: `execSync(['curl', '-L', '-f', '-o', tmpFile, url], ...)` and `execSync(['tar', 'xzf', tmpFile, '-C', pkgDir], ...)` using `shell: false`. This prevents shell metacharacter interpretation. Verify by setting `tmpFile` or `url` to a value containing `; echo pwned` and confirming no injection occurs.
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 name `r` into a shell command, allowing injection. Replace with `execSync(['git', 'rev-parse', '--verify', '--quiet', r], { stdio: 'pipe', encoding: 'utf-8', shell: false })` using an array argument list. This treats `r` as a literal argument rather than shell code. Verify by passing a ref like `HEAD; malicious-command` and confirming it is rejected as an invalid ref.
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 file paths and URLs into shell strings without escaping. Replace `execSync(\`curl -L -f -o "${tmpFile}" "${url}"`...)` with `execSync(['curl', '-L', '-f', '-o', tmpFile, url], { shell: false })` and `execSync(\`tar xzf "${tmpFile}" -C "${pkgDir}"`...)` with `execSync(['tar', 'xzf', tmpFile, '-C', pkgDir], { shell: false })`. This prevents shell interpretation of special characters in paths and URLs. Verify by injecting a path like `/tmp/file; rm -rf /` and confirming the command treats it as a literal path.
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 into a shell command, allowing injection. Replace with `execSync(['git', 'show', `:${filepath}`], { encoding: 'utf-8', shell: false })` using an array argument list. This treats `filepath` as a literal argument. Verify by passing a filepath like `HEAD; malicious-command` and confirming it is rejected as an invalid git reference.
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))}\`, ...)` interpolates a path into a shell command, which can be exploited if the path contains shell metacharacters. Replace with `execSync(['git', 'add', path.relative(ROOT, entryPath)], { cwd: ROOT, shell: false })` using an array argument list. This prevents shell interpretation. Verify by creating a file with a name like `file; rm -rf` and confirming it is added 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
| 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))}\`, ...)` interpolates a path into a shell command. Replace with `execSync(['git', 'add', path.relative(ROOT, entryPath)], { cwd: ROOT, shell: false })` using an array argument list. This treats the path as a literal argument, preventing shell injection. Verify by creating a file with shell metacharacters in its name and confirming it is staged without command injection.
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` into a shell command, allowing injection. Replace with `execSync(['git', 'diff', '--name-only', `${remoteBranch}...HEAD`], { encoding: 'utf-8', shell: false })` using an array argument list. This prevents shell interpretation of `remoteBranch`. Verify by setting `remoteBranch` to a value like `origin/main; malicious-command` and confirming it is treated as a literal ref.
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 filename into a shell command without quoting, allowing injection. Replace with `execSync(['git', 'show', `:${file}`], { encoding: 'utf-8', shell: false })` using an array argument list. This treats `file` as a literal argument. Verify by passing a filename like `HEAD; malicious-command` and confirming it is rejected as an invalid git reference.
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, with no guarantee the destination is trusted. Require the operator to explicitly whitelist the recall server endpoint in a config file or environment variable with a checksum/signature, and log all outbound requests with destination and payload size to stderr. Add a `--dry-run` flag to preview what will be sent before transmission. This ensures the operator controls where user data is sent. Verify by checking that the tool refuses to POST unless the destination is whitelisted and that a dry-run shows the intended recipient.
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 `INSTAR_AUTH_TOKEN` from `process.env` to authenticate all requests, regardless of the caller's identity or permissions. Replace the global token with a per-request authentication mechanism: read the caller's credentials from a secure context (e.g., a signed JWT in the MCP request metadata or a local keychain) and use those credentials to authenticate the recall request. This ensures each caller's identity is preserved. Verify by running the tool with different caller identities and confirming each uses its own credentials.
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 untrusted `contextText` from /internal/prompt-recall verbatim to stdout without any source attribution or delimiter, making it indistinguishable from legitimate context. Wrap the returned context in a clearly marked block (e.g., `<!-- INSTAR RECALL [timestamp] -->[content]<!-- END INSTAR RECALL -->`) and include metadata (source URL, fetch timestamp, content hash). This makes the external origin transparent. Verify by inspecting the stdout output and confirming the recall context is clearly marked as external.
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 named like a secret and written to a file without redaction in logs or output. Rename the variable to `privateKeyPem` (if the naming is accurate) or, if it is genuinely sensitive, wrap the file write with a redaction helper: use a logging library that redacts secrets (e.g., `winston` with a redaction plugin) and avoid logging the path or key content. Add a comment `// Contains sensitive key material — do not log` above the write. Verify by checking logs and console output to confirm the private key content is never printed.
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` is used without disabling entity expansion, allowing XXE attacks to read local files or cause SSRF. Replace the parse call with `ET.parse(sys.argv[1], ET.XMLParser(resolve_entities=False)).getroot()` or use `defusedxml.ElementTree.parse()` instead. This disables external entity resolution. Verify by attempting to parse an XML file with an external entity reference (e.g., `<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>`) and confirming it is rejected or the entity is not resolved.
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 documentation in SKILL.md contains a hardcoded example Anthropic API key (`sk-ant-api03-test1234567890...`). Remove the full key from the file and replace it with a placeholder like `sk-ant-api03-[REDACTED]` or `sk-ant-api03-YOUR_KEY_HERE`. If this key was ever committed to the repository, rotate it immediately in the Anthropic console. Verify by running a secret scanner (e.g., `truffleHog` or `git-secrets`) on the repository and confirming no valid API keys are found.
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 documentation lists example API keys (OpenAI, Anthropic, AWS, GitHub, Stripe) in a table, which could be mistaken for real credentials or used as a reference for attackers. Replace all example keys with clearly marked placeholders (e.g., `sk-proj-[REDACTED]`, `AKIA[REDACTED]`, `ghp_[REDACTED]`) and add a note stating 'These are examples only; never commit real credentials.' Verify by scanning the file with a secret detector and confirming no valid credentials are detected.
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 documentation contains example API key formats (OpenAI, Anthropic, AWS, GitHub, Stripe) that could be mistaken for real credentials. Replace all example keys with clearly marked placeholders (e.g., `sk-proj-[REDACTED]`, `sk-ant-api03-[REDACTED]`, `AKIA[REDACTED]`, `ghp_[REDACTED]`, `sk_live_[REDACTED]`) and add a warning: 'These are examples only; never commit real credentials to source control.' Verify by running a secret scanner on the file and confirming no valid credentials are detected.
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 `sys.argv[2]` (a user-controlled filename) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences to hide or rewrite output. Sanitize the filename before printing by removing or escaping ANSI escape sequences: use `filename.replace('\x1b', '').replace('\033', '')` or a library like `blessed.sequences.strip_ansi()`. Replace `print(f"Written: {sys.argv[2]} ...")` with `print(f"Written: {sanitize_ansi(sys.argv[2])} ...")`. Verify by passing a filename containing ANSI sequences (e.g., `file\x1b[2J.txt`) and confirming the output is not corrupted.
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 `job.slug` is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the slug before printing: replace `console.log(\` wrote ${job.slug}.md ...\`)` with `console.log(\` wrote ${stripAnsi(job.slug)}.md ...\`)` using a library like `strip-ansi`. Verify by creating a job with a slug containing ANSI sequences (e.g., `job\x1b[2J`) and confirming the output is not corrupted.
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 `sys.argv[1]` (a user-controlled URL) is passed to `urlparse()` and the result is printed without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the hostname before printing: replace `print((urlparse(sys.argv[1]).hostname or '').lower())` with `hostname = (urlparse(sys.argv[1]).hostname or '').lower(); print(hostname.replace('\x1b', '').replace('\033', ''))` or use a library like `re.sub(r'\x1b\[[0-9;]*m', '', hostname)`. Verify by passing a URL with ANSI sequences 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 `sys.argv[2]` (a user-controlled file path) is passed to `sign_file()` and the result is printed without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the output before printing: replace `print(sign_file(sys.argv[2]))` with `result = sign_file(sys.argv[2]); print(result.replace('\x1b', '').replace('\033', ''))` or use a library like `re.sub(r'\x1b\[[0-9;]*m', '', result)`. Verify by passing a file path with ANSI sequences and confirming the printed signature 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 `decodePayload(reply.payload).substring(0, 70)` (user-controlled message content) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the message before printing: replace `console.log(\` 💬 ${name}: "${decodePayload(reply.payload).substring(0, 70)}...\`)` with `const msg = stripAnsi(decodePayload(reply.payload).substring(0, 70)); console.log(\` 💬 ${name}: "${msg}...\`)` using a library like `strip-ansi`. Verify by sending a message with ANSI sequences and confirming the output is not corrupted.
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 error messages and paths are printed to stderr without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize all user-controlled values before printing: replace `console.error('A Tier-1 trace requires --eli16-path (the request ELI16 overview).')` with `console.error(stripAnsi('A Tier-1 trace requires --eli16-path (the request ELI16 overview).'))` and similarly for any path variables. Use a library like `strip-ansi`. Verify by passing arguments with ANSI sequences and confirming error 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 `sys.argv[3]` (a user-controlled domain name) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the domain before printing: replace `print(f"Added strategy in {sys.argv[3]} domain. Total: {len(data['strategies_discovered'])}")` with `domain = sys.argv[3].replace('\x1b', '').replace('\033', ''); print(f"Added strategy in {domain} domain. Total: {len(data['strategies_discovered'])}")` or use `re.sub(r'\x1b\[[0-9;]*m', '', sys.argv[3])`. Verify by passing a domain with ANSI sequences 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 `result['body']` (potentially user-controlled content) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the body before printing: replace `print(result['body'])` with `body = result['body'].replace('\x1b', '').replace('\033', ''); print(body)` or use `re.sub(r'\x1b\[[0-9;]*m', '', result['body'])`. Verify by fetching content with ANSI sequences 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 `sys.argv[3]` (a user-controlled HMAC value) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the HMAC before printing: replace `print(chain_sign(sys.argv[2], sys.argv[3]))` with `result = chain_sign(sys.argv[2], sys.argv[3]); print(result.replace('\x1b', '').replace('\033', ''))` or use `re.sub(r'\x1b\[[0-9;]*m', '', result)`. Verify by passing an HMAC with ANSI sequences 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 `sys.argv[2]` (a user-controlled operation name) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the operation before printing: replace `print(f"Usage: playbook-history.py append <operation> <item_id> <source_session> [--payload '{{}}']" ...)` with `operation = sys.argv[2].replace('\x1b', '').replace('\033', ''); print(f"Usage: playbook-history.py append {operation} ...")` or use `re.sub(r'\x1b\[[0-9;]*m', '', sys.argv[2])`. Verify by passing an operation with ANSI sequences 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 `sys.argv[2]` (a user-controlled session ID) is printed directly to stdout without ANSI escape sanitization, allowing injection of cursor-control sequences. Sanitize the session ID before printing: replace `print(summary(sys.argv[2]))` with `result = summary(sys.argv[2]); print(result.replace('\x1b', '').replace('\033', ''))` or use `re.sub(r'\x1b\[[0-9;]*m', '', result)`. Verify by passing a session ID with ANSI sequences 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 mentions a risk of plaintext credential leakage via `console.log(JSON.stringify(body))`, which could print sensitive data without ANSI escape sanitization. Replace any such logging with a sanitized version: `console.log(stripAnsi(JSON.stringify(body)))` using a library like `strip-ansi`, and add a helper function to redact known secret fields before logging. Verify by attempting to log a JSON object with credentials and confirming they are redacted or 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` clause silently swallows all exceptions, hiding failures from incident response and debugging. Replace with `except Exception as e: logging.error(f"playbook-semantic-verify: verification failed: {e}", exc_info=True)` to log the exception with full traceback. This ensures failures are visible in logs. Verify by triggering an exception (e.g., by corrupting the VERIFY_LOG file) and confirming it is logged with a traceback.
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` clause silently discards malformed JSON lines, hiding data corruption or parsing errors. Replace with `except json.JSONDecodeError as e: logging.warning(f"build-state: skipping malformed JSON line: {line[:50]}... ({e})")` to log the error. This ensures data quality issues are visible. Verify by adding a malformed JSON line to AUDIT_LOG and confirming it is logged as a warning.
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 acknowledges silent error swallowing as a root cause of the PromptGate incident but does not provide a concrete fix. Replace all bare `catch {}` blocks with explicit error handling: `catch (err) { logger.warn('Context: error details', { error: err.message, stack: err.stack }); }` and ensure all errors are logged with context. Add a linting rule to reject empty catch blocks. Verify by running the linter on the codebase and confirming no empty catch blocks remain.
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` clause silently swallows exceptions from the telegram-reply subprocess call, hiding failures in the review notification system. Replace with `except Exception as e: logging.warning(f"playbook-lifecycle: failed to send telegram reply: {e}")` to log the error. This ensures notification failures are visible. Verify by simulating a subprocess failure (e.g., by making telegram-reply.py unavailable) and confirming it 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` clause silently swallows exceptions when appending to the history log, hiding data persistence failures. Replace with `except Exception as e: logging.error(f"playbook-retirement: failed to append history entry: {e}", exc_info=True)` to log the error with traceback. This ensures failures are visible for incident investigation. Verify by simulating a write failure (e.g., by making the history file read-only) and confirming it is logged with a traceback.