Severity: 🔴 Critical (silent secret leak in audit log)
src/perseus/directives/query.py writes the executed command verbatim to audit_log.jsonl via audit_event(... command=cmd[:500] ...). The audit log path does NOT pipe values through redact_text. Render-time redaction only applies to the final rendered output, not to audit fields.
If a user writes @query "curl -H 'Authorization: Bearer ghp_…'", the rendered output is correctly redacted, but ~/.perseus/audit_log.jsonl contains the raw bearer token forever.
Affected paths (in directives/query.py)
- Line ~91:
audit_event(cfg, "shell_exec", command=cmd[:500], ...) — leaks cmd
- Line ~119:
header = f"> ⚠ \@query` exited {exit_code}: `{cmd}`\n\n"` — leaks cmd (rendered, but later in the pipeline than audit)
- Line ~121:
body = stdout or stderr or "(no output)" — leaks stderr containing tokens
- Line ~126:
return f"> (no output from \{cmd}`)"` — leaks cmd
- Line ~161:
return f"> ⚠ \@query` timed out ({timeout}s): `{cmd}`"` — leaks cmd
- Line ~165:
return f"> ⚠ \@query` error: {exc}"` — exc.args often includes cmd
Repro
cat > .perseus/context.md <<'PCM'
@query "echo 'AKIAIOSFODNN7EXAMPLE'"
PCM
perseus render
grep AKIA ~/.perseus/audit_log.jsonl
# Expected: nothing
# Actual: audit entry with raw AKIA key
Suggested fix
- In
audit.py::audit_event, pipe every field value (or at least command, args, error, stdout, stderr) through redact_text before writing.
- In
directives/query.py, also call redact_text(cmd, cfg) and redact_text(stderr, cfg) before interpolating into error messages.
Acceptance criteria
- Test:
@query with an AWS key in the command produces an audit entry with [REDACTED:aws_access_key_id] in place of the key.
- Same for timeout, error, and exit-nonzero paths.
Severity: 🔴 Critical (silent secret leak in audit log)
src/perseus/directives/query.pywrites the executed command verbatim toaudit_log.jsonlviaaudit_event(... command=cmd[:500] ...). The audit log path does NOT pipe values throughredact_text. Render-time redaction only applies to the final rendered output, not to audit fields.If a user writes
@query "curl -H 'Authorization: Bearer ghp_…'", the rendered output is correctly redacted, but~/.perseus/audit_log.jsonlcontains the raw bearer token forever.Affected paths (in
directives/query.py)audit_event(cfg, "shell_exec", command=cmd[:500], ...)— leaks cmdheader = f"> ⚠ \@query` exited {exit_code}: `{cmd}`\n\n"` — leaks cmd (rendered, but later in the pipeline than audit)body = stdout or stderr or "(no output)"— leaks stderr containing tokensreturn f"> (no output from \{cmd}`)"` — leaks cmdreturn f"> ⚠ \@query` timed out ({timeout}s): `{cmd}`"` — leaks cmdreturn f"> ⚠ \@query` error: {exc}"` — exc.args often includes cmdRepro
Suggested fix
audit.py::audit_event, pipe every field value (or at leastcommand,args,error,stdout,stderr) throughredact_textbefore writing.directives/query.py, also callredact_text(cmd, cfg)andredact_text(stderr, cfg)before interpolating into error messages.Acceptance criteria
@querywith an AWS key in the command produces an audit entry with[REDACTED:aws_access_key_id]in place of the key.