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 checkout — git 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
- 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
- Run
./batch/batch-runner.sh
- 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
- 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).
- Add the URL to
batch/batch-input.tsv.
- Run
./batch/batch-runner.sh.
- 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
- 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_
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
batch/batch-runner.sh:301-307sed— URL/ID values are interpolated directly into sed replacement patterns without escaping. A URL containing&,\, or|breaks out of the pattern.batch/batch-runner.sh:312--dangerously-skip-permissions— Every spawnedclaude -pworker runs with all permission checks bypassed, meaning any prompt injection in a job description can execute arbitrary shell commands,High
batch/batch-runner.sh:284/tmppaths — Uses/tmp/batch-jd-{id}.txtinstead ofmktemp. Symlink race (TOCTOU) can redirect reads/writes to arbitrary files.update-system.mjs:172-272git branch --listare interpolated into shell-executedgitcommands without validation.update-system.mjs:187-194git checkout FETCH_HEAD -- {path}writes files from the remote with no integrity verification (no GPG signature, no checksum).generate-pdf.mjs:94-95resolve()but never checked to stay within the project.../../anywhere.pdfworks.check-liveness.mjs:56-100file://,localhost, and internal network addresses.Medium
merge-tracker.mjs:272,285|and newlines in company names/roles break table structure. Can corruptapplications.md..bakfiles not gitignored — Backup files with application data could be accidentally committed.Steps to reproduce
#1 — sed injection
batch/batch-input.tsvwith a URL containing a sed metacharacter:1\thttps://example.com/job?id=1&role=test\tmanual\tnotes
./batch/batch-runner.sh&in the URL causessedto 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
batch/batch-input.tsv../batch/batch-runner.sh.--dangerously-skip-permissionsis 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