Repository navigation
AuditLogger.log_event lets caller keyword arguments overwrite envelope fields (run_id, operator, timestamp, prev_hash); the library guide's run_id= example breaks HMAC verification (CR-A07-009) #377
Description
Wave 1 · theme D1-2: Audit chain, integrity manifest and verifier robustness · highest severity High · 3 finding(s) · effort small
Summary
An orchestrator integration follows the guide and logs run_id=; every later verify-audit with the secret exits 6 although nothing was tampered with (or, without a secret, the ForgeLM run_id is overwritten so the chain cannot be correlated). A library caller relaying external values can spoof operator/timestamp attribution in an entry that verifies.
Primary finding
CR-A07-009 — AuditLogger.log_event lets caller keyword arguments overwrite envelope fields (run_id, operator, timestamp, prev_hash); the library guide's run_id= example breaks HMAC verification
severity High · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end
What happens. log_event builds the entry as {timestamp, run_id, operator, event, prev_hash, **details} (forgelm/compliance.py:552-562), so a details key with an envelope name silently replaces the reserved value. The writer signs with the key derived from the logger's own run_id (258), while the verifier derives the key from the entry's run_id (2429). A different run_id= therefore yields a permanent false 'HMAC mismatch' (exit 6). operator= and timestamp= misattribute a line that still verifies, and prev_hash= breaks the chain. docs/guides/library_api.md:111-118 passes run_id=os.environ['RUN_ID'].
Where.
forgelm/compliance.py:552-560docs/guides/library_api.md:111-118(confirmed)forgelm/compliance.py:555-562forgelm/compliance.py:258forgelm/compliance.py:2428-2429forgelm/compliance.py:2513-2519docs/guides/library_api-tr.md:112-119forgelm/trainer.py:1550forgelm/cli/subcommands/_approve.py:617forgelm/compliance.py:2419-2432
Evidence.
- compliance.py:552-562 (entry construction, **details at 561). s3_audit.py: D1 run_id 'airflow-run-1' with HMAC -> 'line 5: HMAC mismatch', CLI verify-audit exit 6; D3 operator='someone-else', timestamp='1999-01-01...' -> valid=True; D4 prev_hash='genesis' -> chain broken. docs/guides/library_api.md:93-101.
- Repro: a guide-pattern logger (auto run_id fg-25a0b3a47929) writes run_id='airflow-run-5678'. Chain-only verify passes. With the secret: 'line 3: HMAC mismatch', classified integrity. CLI
forgelm verify-auditexits 6 with the secret and 0 without. Fresh-log override: 'manifest mismatch: pinned run_id 'fg-…' does not match line 1 run_id 'other-run''. operator='someone-else' and timestamp='1999-…' are written and pass require_hmac. - scratch/reserved.py: log_event('a.two', operator='spoofed-operator', timestamp='1999-01-01...') is written as given; log_event('a.three', prev_hash='deadbeef') yields verify_audit_log -> valid=False 'chain broken at line 3'.
Recommended fix. Put **details first and the reserved fields after (or reject reserved keys with ValueError); rename the guide's field (e.g. orchestrator_run_id); add a regression test.
Other findings with the same root cause
These close with the same fix; update them in the same pull request.
DOC-D2B04-006 — docs/guides/library_api.md: broken example — Emit your own orchestrator event into the same chain with `logger.log_event("training.completed", orchestrator="airflow", dag_id="train", run_id=os.enviro…
severity High
Where. docs/guides/library_api.md:107-118 (Turkish mirror: docs/guides/library_api-tr.md:108-119) · type: broken example
The document says. Emit your own orchestrator event into the same chain with logger.log_event("training.completed", orchestrator="airflow", dag_id="train", run_id=os.environ["RUN_ID"], outcome="success" if result.success else "reverted").
What the code does. log_event builds the entry as {timestamp, run_id: self.run_id, operator, event, prev_hash, **details}, so the run_id= kwarg silently overwrites the entry's run_id. The HMAC key is derived from the logger's own run_id (sha256(secret + self.run_id)), but the verifier derives the key from the entry's run_id. Result with FORGELM_AUDIT_SECRET set: verify_audit_log(..., hmac_secret=...) -> 'line 2: HMAC mismatch', and forgelm verify-audit prints 'FAIL at line 2: line 2: HMAC mismatch' and exits 6, the tampering code (runtime-confirmed). Without a secret, the entry is attributed to a foreign run_id. Two smaller problems: 'training.completed' is not an event in docs/reference/audit_event_catalog.md, and result.success == False does not imply a revert (TrainResult has a separate reverted field).
Code references. forgelm/compliance.py:555-561, forgelm/compliance.py:258, forgelm/results.py (TrainResult.reverted)
User impact. Following the guide makes a legitimate audit log fail verification permanently with the tamper exit code (6) once HMAC is enabled. That destroys the evidentiary value of the Art. 12 record and raises false security alarms.
Proposed correction. Doc: use a non-reserved key and a namespaced event, e.g. logger.log_event("orchestrator.run_linked", orchestrator="airflow", dag_id="train", orchestrator_run_id=os.environ["RUN_ID"], forgelm_run_id=result.run_id, outcome="reverted" if result.reverted else ("success" if result.success else "failed")). Add a warning that timestamp, run_id, operator, event and prev_hash are reserved. Code: fix log_event so it rejects (ValueError) or namespaces reserved keys in **details.
Turkish mirror. the Turkish page has the same error; fix both in the same PR
DOC-D2B04-018 — docs/guides/library_api.md: misleading — log_event('training.completed', ...) is an event the audit chain accepts
severity Medium
Where. docs/guides/library_api.md:93-101 · type: misleading
The document says. log_event('training.completed', ...) is an event the audit chain accepts
What the code does. training.completed is not in docs/reference/audit_event_catalog.md (only training.started); log_event does not validate names, and the example's run_id= keyword clobbers the envelope run_id.
Code references. docs/reference/audit_event_catalog.md:31, forgelm/compliance.py:561
User impact. Uncatalogued event plus an HMAC-breaking keyword in the recommended pattern.
Proposed correction. Use a catalogued event and a non-reserved key name; fix log_event to protect reserved fields.
Turkish mirror. the Turkish page has the same error; fix both in the same PR
Acceptance criteria
- A regression test reproduces the failure scenario and asserts the corrected behaviour.
- Documentation listed above is corrected in English and in its Turkish mirror in the same PR.
- Every finding in this issue is resolved or explicitly closed with a reason.
Found by the October 2026 ForgeLM code and documentation review against commit cab2947. Code links point at that commit; line numbers may have moved since. Finding IDs (CR-…, DOC-…, TECH-…) are stable references for this backlog.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity