Skip to content

security checks: fix injection vectors, SSRF, path traversal, and unsafe permissions in batch/update/PDF/liveness scripts #132

@dean985

Description

@dean985

What happened?

i've identified several vulnerabilities in the local tooling scripts. All are exploitable when processing untrusted input (job URLs, company names, crafted VERSION files).
None require network access to exploit - they trigger during normal batch processing or update flows.

Critical

# File Vulnerability Impact
1 batch/batch-runner.sh:301-307 Shell injection via sed — URL/ID values are interpolated directly into sed replacement patterns without escaping. A URL containing &, \, or | breaks out of the pattern. Arbitrary
command execution as the current user
2 batch/batch-runner.sh:312 --dangerously-skip-permissions — Every spawned claude -p worker runs with all permission checks bypassed, meaning any prompt injection in a job description can execute arbitrary shell commands,
write files anywhere, etc. Full system access from any worker

High

# File Vulnerability Impact
3 batch/batch-runner.sh:284 Predictable /tmp paths — Uses /tmp/batch-jd-{id}.txt instead of mktemp. Symlink race (TOCTOU) can redirect reads/writes to arbitrary files. File read/write via symlink
4 update-system.mjs:172-272 Git command injection — Version strings from VERSION file and branch names from git branch --list are interpolated into shell-executed git commands without validation. Arbitrary command
execution if VERSION is tampered
5 update-system.mjs:187-194 Unvalidated remote checkoutgit checkout FETCH_HEAD -- {path} writes files from the remote with no integrity verification (no GPG signature, no checksum). Malicious code written to disk if
upstream is compromised
6 generate-pdf.mjs:94-95 Path traversal — Output path from CLI args is used with resolve() but never checked to stay within the project. ../../anywhere.pdf works. Write PDF to arbitrary filesystem location
7 check-liveness.mjs:56-100 SSRF — Playwright navigates to any URL passed in, including file://, localhost, and internal network addresses. Read local files, probe internal services

Medium

# File Vulnerability Impact
8 merge-tracker.mjs:272,285 Markdown table injection — Pipe chars | and newlines in company names/roles break table structure. Can corrupt applications.md. Data corruption, potential markdown injection
9 Various scripts .bak files not gitignored — Backup files with application data could be accidentally committed. Data exposure

Steps to reproduce

#1 — sed injection
  1. Add an entry to batch/batch-input.tsv with a URL containing a sed metacharacter:
    1\thttps://example.com/job?id=1&role=test\tmanual\tnotes
  2. Run ./batch/batch-runner.sh
  3. The & in the URL causes sed to insert the entire matched line into the replacement, corrupting the resolved prompt. With a crafted payload (e.g. a URL containing backtick-wrapped commands), this escalates to command execution.
#2 — dangerously-skip-permissions
  1. Create a job posting page that includes prompt injection text in the job description (e.g. hidden text instructing the AI to run a shell command).
  2. Add the URL to batch/batch-input.tsv.
  3. Run ./batch/batch-runner.sh.
  4. Because --dangerously-skip-permissions is set, the spawned claude worker will execute any shell command the injected prompt requests — file reads, writes, network calls, etc. — with zero user confirmation.
#3 — predictable /tmp paths
  1. Before running the batch runner, create a symlink:
ln -s ~/.ssh/id_rsa /tmp/batch-jd-1.txt
2. Run ./batch/batch-runner.sh with an entry using ID 1.
3. The worker reads ~/.ssh/id_rsa instead of the expected JD file, potentially sending its contents to the API as prompt context.

1. Edit the VERSION file to contain a shell metacharacter:
1.0.0$(touch /tmp/pwned)
2. Run node update-system.mjs apply.
3. The backup branch command becomes git branch backup-pre-update-1.0.0$(touch /tmp/pwned), and the subshell executes.

1. Fork santifer/career-ops on GitHub.
2. In the fork, modify generate-pdf.mjs (a system-layer file) to include malicious code.
3. If a user's git remote points to the fork (or the canonical repo is compromised), running node update-system.mjs apply will silently overwrite local scripts with the malicious versions. No signature or checksum is verified.

1. Run:
node generate-pdf.mjs input.html ../../../tmp/evil.pdf
2. The PDF is written to /tmp/evil.pdf. Any writable path on the filesystem is reachable.

1. Run:
node check-liveness.mjs http://localhost:6379/
1. or:
node check-liveness.mjs file:///etc/passwd
2. Playwright navigates to the URL and reads the response. Internal services, local files, and metadata endpoints (e.g. cloud instance metadata at 169.254.169.254) are all reachable.

1. Create a TSV file in batch/tracker-additions/ where the company name contains a pipe:
42\t2025-01-15\tFoo | Bar Corp\tEngineer\tEvaluated\t4.0/5\t❌\t[42](reports/042-foo-2025-01-15.md)\tnotes
2. Run node merge-tracker.mjs.
3. The pipe splits the table cell, adding a phantom column and corrupting every row after it in applications.md.

1. Run node merge-tracker.mjs or node dedup-tracker.mjs (both create .bak backups).
2. Run git add -A && git status.
3. The .bak file containing full application data (company names, scores, notes) is staged for commit.

### Expected behavior


1. sed substitution should be safe regardless of what characters appear in URLs or IDs.
2. Batch workers should run with standard permission checks, not bypass them entirely.
3. Temp files should use unpredictable, atomically-created paths (mktemp).
4. Git commands should not be vulnerable to injection via VERSION file contents or branch names.
5. Remote updates should validate version format before trusting fetched data.
6. PDF output should be constrained to the project directory or output/.
7. Playwright navigation should be restricted to public HTTP(S) URLs only.
8. Markdown table output should escape pipe and newline characters in dynamic cell values.
9. .bak files should be gitignored to prevent accidental commits of user data.

### OS

_No response_

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions