Skip to content

reverse-pii answers an Art. 15 request with exit 0 and no audit event when operator identity is unresolvable, even with an explicit --audit-dir (CR-A10-013) #386

Open

Description

@cemililik

Wave 1 · theme D1-3: Revert, approval and GDPR erasure safety · highest severity High · 3 finding(s) · effort small

Summary

FORGELM_OPERATOR is unset and getpass.getuser() raises KeyError (stubbed). forgelm reverse-pii --query ada0@example.com --output-dir noop --audit-dir noop/audit --output-format json noop/train.jsonl exits 0 with success:true and the match snippet. noop/audit/ stays empty. The same condition makes purge exit 1.

Primary finding

CR-A10-013 — reverse-pii answers an Art. 15 request with exit 0 and no audit event when operator identity is unresolvable, even with an explicit --audit-dir

severity Medium · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end · already tracked: KNOWN-OPEN TRUST-14 — docs/roadmap/phase-16-trust-surface-hardening.md:574-575,989-990 (S9 open; unit id inferred)

What happens. _maybe_audit_logger (_reverse_pii.py:545-558) catches the ConfigError AuditLogger raises when FORGELM_OPERATOR is unset and getpass fails, logs a WARNING and returns None regardless of whether --audit-dir was explicit; _scan_with_audit then skips both audit emits (:655, :689) and the dispatcher prints a success envelope with raw matches. Module header, docs and purge (which exits 1 on the same ConfigError, _purge.py:736-739) all promise fail-closed behaviour.

Where.

Evidence.

  • logs/Q10a_rpii_noop_explicit_auditdir.{out,err}, logs/Q10b_purge_noop.out, noop_stub.py in outputs
  • _reverse_pii.py:550-558 except ConfigError as exc: logger.warning(... 'access request will run but the regulatory chain will NOT record it' ...) return None
  • r1/h09.py sets FORGELM_OPERATOR to an empty value and patches getpass.getuser to raise KeyError (the arbitrary-UID container case), then runs reverse-pii --audit-dir r1/audit --output-format json. Result: WARNING 'AuditLogger init refused ... access request will run but the regulatory chain will NOT record it', EXITCODE 0, success envelope with match_count 1 and a raw snippet, and AUDIT_DIR_CONTENTS [].

Recommended fix. Treat ConfigError like every other AuditLogger failure: exit 1 or 2 before scanning, at least when --audit-dir is explicit, matching purge. Otherwise document the exception.

Other findings with the same root cause

These close with the same fix; update them in the same pull request.

DOC-D1B05-006 — docs/reference/reverse_pii_subcommand.md: false claim — Every invocation emits exactly one data.access_request_query event; explicit --audit-dir values fail loudly rather than silently dropping t…

severity High · already tracked: KNOWN-OPEN TRUST-01/TRUST-07/TRUST-13/TRUST-14 (S9; per-unit split not public) — docs/roadmap/phase-16-trust-surface-hardening.md:574-575

Where. docs/reference/reverse_pii_subcommand.md:68,26 (Turkish mirror: docs/reference/reverse_pii_subcommand-tr.md:26,68) · type: false claim

The document says. Every invocation emits exactly one data.access_request_query event; explicit --audit-dir values fail loudly rather than silently dropping the Article 15 forensic record

What the code does. When AuditLogger raises ConfigError (no FORGELM_OPERATOR, getpass fails, no anonymous opt-in) reverse-pii logs a WARNING and runs the scan with no audit record, exit 0, even with an explicit --audit-dir. Runtime (getpass patched to fail, --audit-dir given): EXIT 0, audit_log.jsonl absent, raw snippet printed.

Code references. forgelm/cli/subcommands/_reverse_pii.py:545-558, forgelm/cli/subcommands/_reverse_pii.py:689-705, forgelm/compliance.py:219-229

User impact. In arbitrary-UID containers without FORGELM_OPERATOR, Article 15 searches over personal data (with raw snippets on stdout) leave no audit-chain record, while the reference promises one per invocation; purge and approve refuse in the same situation.

Proposed correction. Fix the code: treat ConfigError like every other AuditLogger failure (exit 1 or 2) so no access request runs unrecorded. Interim doc warning: "If the operator identity cannot be resolved (no FORGELM_OPERATOR, no resolvable username, no FORGELM_ALLOW_ANONYMOUS_OPERATOR=1), the scan still runs and NO audit event is written; set FORGELM_OPERATOR in CI/containers."

Turkish mirror. the Turkish page has the same error; fix both in the same PR

DOC-D1B05-023 — docs/reference/reverse_pii_subcommand.md: false claim — Every invocation writes data.access_request_query; explicit --audit-dir never silently drops the record

severity Medium · already tracked: KNOWN-OPEN TRUST-01/TRUST-07/TRUST-13/TRUST-14 (S9) — docs/roadmap/phase-16-trust-surface-hardening.md:574-575

Where. docs/reference/reverse_pii_subcommand.md:68 ('Every invocation emits exactly one event'), :26, :82; docs/guides/gdpr_erasure.md:165-168 · type: false claim

The document says. Every invocation writes data.access_request_query; explicit --audit-dir never silently drops the record

What the code does. When the operator identity is unresolvable (ConfigError), the scan succeeds with no event (Q10a, stubbed). See finding F4.

Code references. forgelm/cli/subcommands/_reverse_pii.py:545-558

Proposed correction. Fail closed (see F4), or document the exception.

Turkish mirror. the Turkish page has no such statement

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

No one assigned

    Labels

    area: gdpr-approvalCode area: gdpr-approvalbacklog: knownAlready tracked in docs/roadmapblockedWaits for another issue (see Blocked by); set and removed automatically, never by handbugSomething isn't workingdocumentationImprovements or additions to documentationseverity: highWrong behaviour or a false claim users rely onsource: review-2026-10Found by the October 2026 code and documentation reviewwave: 1Roadmap wave 1: high severity, small effort

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions