fix(api): redact secrets in run debug data instead of storing them in clear text - #153
fix(api): redact secrets in run debug data instead of storing them in clear text#153arminfauland wants to merge 2 commits into
Conversation
… clear text
The debug data collected for a run contains the resolved variables, and those
include secrets. It was serialised and written to scrape_data verbatim, so a
login password ended up in the database in clear text:
sqlite> SELECT value FROM scrape_data WHERE key='__debugData';
{"var_email":"user@example.com","var_password":"<clear text>", …}
GET /api/runs/:runId/debug then hands that row out unchanged. Anything else
reading it — a backup, a database dump, the artifacts endpoint — does the same.
The project already has SecretRedactionService, and ScrapeExecutionService
already has it injected; the debug path just never used it.
Redaction is applied at the source, before the row is written, rather than on
the way out. Redacting only on read would leave the plaintext at rest, where
backups and dumps still pick it up.
Both read paths (debug and artifacts) redact as well, as a safety net for rows
written before this change. That only works while the secrets are still
registered in the running process, so it is explicitly not a substitute for
the fix at the source — existing rows should be cleared:
DELETE FROM scrape_data WHERE key = '__debugData';
Tests: one at the source proving the plaintext never reaches storeData, one on
the endpoint proving it is not handed out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LixHBPkhb8h5oDdMqSG4se
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| - | - | Generic Password | 51e7d18 | apps/api/src/scrape/services/scrape-execution.service.spec.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
GitGuardian flagged the previous test value as a "Generic Password" on PR Disane87#153. It was never a real credential — an invented string — but sitting next to a `var_password` key it looks like one to a scanner, and a false positive on a security PR costs the reviewer attention. Replaced with `<<fixture-not-a-real-secret>>`, which reads as a placeholder to humans and scanners alike. Both tests still assert the same thing: the value must not appear in what is stored, and must not appear in what the endpoint returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LixHBPkhb8h5oDdMqSG4se
|
The GitGuardian finding is a false positive, and I have removed the cause. What it flagged: the string in the new test fixture, sitting next to a Verified rather than assumed before replying: the string does not appear in any secret store on the machine this was written on (encrypted secrets table: 0 hits, scrape data: 0 hits, all env and compose files: 0 hits). I also scanned every added line across all five branches I opened today — 569 lines — for Anthropic keys, API keys, GitHub tokens, Telegram bot tokens, AWS keys, private keys, JWTs and long hex strings: zero matches. The only password-shaped assignment anywhere was that one fixture. Fixed in 71eff1c: replaced with Worth noting the irony: this PR exists because real credentials were being written in clear text — into |
Problem
The debug data collected for a run contains the resolved variables — and those include secrets. It is serialised and written to
scrape_dataverbatim, so a login password ends up in the database in clear text:GET /api/runs/:runId/debugthen hands that row out unchanged. So does the artifacts endpoint, and so does every backup and database dump.Found on my own instance:
var_passwordwas a 12-character plaintext string, straight out of the DB.Why the fix is at the source
The project already ships
SecretRedactionService, andScrapeExecutionServicealready has it injected — the debug path simply never called it.Redaction is applied before the row is written, not on the way out. Redacting only on read would leave the plaintext at rest, where backups and dumps still pick it up.
Both read paths (
/debugand/artifacts, which reads the same key) redact as well — a safety net for rows written before this change. That only works while the secrets are still registered in the running process, so it is explicitly not a substitute. Existing rows should be cleared once:Injection
SecretRedactionServiceis exported byEventsModule, which is@Global()— no module wiring needed for the controller.Tests
redacts the debug data before it reaches the databasestoreDatadoes not hand out credentials stored in the debug datanx lint apiclean, full API suite green (84 files / 1418 tests).