Skip to content

Memorisation warning looks only at <output_dir>/final_model, and output_dir defaults to the corpus folder, so a default invocation never warns (CR-A10-022) #102

Closed

Description

@cemililik

Wave 0 · theme D0-6: Personal data: audit and masking coverage · highest severity High · 10 finding(s) · effort medium

Summary

An operator erases a subject's row with the plain documented command. The audit chain records a completed erasure with no memorisation warning, and the DPO concludes that no trained model holds the data. The run's own audit log, under checkpoints/, holds no record of the erasure.

Primary finding

CR-A10-022 — Memorisation warning looks only at <output_dir>/final_model, and output_dir defaults to the corpus folder, so a default invocation never warns

severity Medium · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end

What happens. _detect_warning_conditions fires data.erasure_warning_memorisation only when the literal <output_dir>/final_model exists (_purge.py:401-404). In row mode output_dir defaults to the corpus's parent directory (_purge.py:715), not to training.output_dir, which defaults to ./checkpoints. A loaded --config is used only for webhook targets, never to find training.output_dir or training.final_model_dir. A non-default training.final_model_dir (config.py:453-455, used by trainer.py:1590-1593) is missed even with the correct --output-dir. The same hard-coded name affects staging purge (:970) and the retention scan (:1282). So the documented 'warning when a final_model/ exists for any run that consumed the corpus' (docs/guides/gdpr_erasure.md:22; catalog) does not fire on default use. The implicit fallback also writes the salt, audit_log.jsonl and the genesis manifest next to the corpus, in a separate chain from the run's audit log. docs/reference/purge_subcommand.md:38 and docs/usermanuals/en/compliance/gdpr-erasure.md:117 say this fallback 'emits a WARNING naming the resolved dir', but purge emits none. HF datasets Arrow-cache copies are outside the documented scope (design §6 scope limitation), so they are not counted here.

Where.

Evidence.

  • Repro r7. (1) Corpus at r7/data/train.jsonl, trained model at r7/checkpoints/final_model, no --output-dir: warnings [], and .forgelm_audit_salt, audit_log.jsonl and its manifest appear in r7/data. (2) --output-dir r7/ck2 holding my_model/ (custom final_model_dir): warnings []. Repro r12: text mode prints only 'Erased 1 row(s); 22 bytes freed.' with no WARNING. rg finds no logger.warning in _purge.py other than the config-load failure path (:1450).

Recommended fix. When --config is given, use training.output_dir and training.final_model_dir to find final models and staging directories. Make the fallback emit the documented WARNING, or require --output-dir. Glob <final_model_dir> and <final_model_dir>.staging.* instead of hard-coding 'final_model'.

Other findings with the same root cause

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

  • CR-A10-042 (code, Medium) — The memorisation warning and retention horizons ignore checkpoint-* dirs, which every completed run leaves behind, and the memorisation warning also ignores staging dirs and exported models: purge re…
    severity Medium · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end
    Failure scenario. Case 1: a run with evaluation.require_human_approval exits 4 and is pending or rejected. Case 2: a run trips auto_revert, which deletes final_model/. In both cases out/ still holds checkpoint-1/ and checkpoint-2/ with LoRA weights trained on the subject's row, plus final_model.staging.<run_id>/ in case 1. forgelm purge --row-id alice --corpus data/train.jsonl --output-dir out exits 0 with warnings [], and the Art. 17 audit chain records no memorisation gap. --check-policy with every horizon a…
    Where. forgelm/cli/subcommands/_purge.py:388-390, forgelm/cli/subcommands/_purge.py:1199-1221, forgelm/trainer.py:509-510, forgelm/trainer.py:509
    Recommended fix. In row mode, raise the memorisation warning, or a sibling event, when any checkpoint-, <final_model_dir>.staging. or configured merge or export output exists under the configured training.output_dir. Read these paths from --config. List the paths in the payload. Add a checkpoint horizon, or say in the docs that checkpoints are outside retention, and correct the docs that promise the operator se…

  • TECH-T11-001 (technology, Now) — Regulation and standards: GDPR, ISO/IEC 27001, SOC 2, ISO/IEC 42001, EDPB, EU Cyber Resilience Act: EDPB Opinion 28/2024 and later EDPB guidance: personal data persisting in trained weights, and the…
    severity Now
    Item. EDPB Opinion 28/2024 and later EDPB guidance: personal data persisting in trained weights, and the limits of ForgeLM's 'GDPR Article 17 erasure' and model-anonymity statements · verdict: incorrect-claim · urgency: Now
    State in ForgeLM. Accurate surfaces: docs/guides/gdpr_erasure.md:22 and docs/usermanuals/en/compliance/gdpr-erasure.md:51/:113 say purge does not unmemorise weights and that retraining is the proper mitigation. purge emits data.erasure_warning_memorisation (forgelm/cli/subcommands/_purge.py:72, :403). Overstated or wrong surfaces: docs/usermanuals/en/compliance/gdpr.md:78 (TR :78), inside the Art. 15 access-reques…
    State of the ecosystem on 2026-10-01. EDPB Opinion 28/2024 was adopted on 17 December 2024 under Art. 64(2) GDPR. Its executive summary says AI models trained with personal data 'cannot, in all cases, be considered anonymous'. Anonymity requires the likelihood of extraction and of obtaining personal data from queries to be 'insignificant', assessed case by case. Para. 31: personal data 'may still remain ‘absorbed’ in the parameters o…
    Recommendation. Fix the docs now. Delete or rewrite gdpr.md:78 and :94 (EN+TR): masking reduces risk but does not make the model anonymous; anonymity needs a case-by-case demonstration (Opinion 28/2024 paras 31 and 38-43 framework); ForgeLM produces no extraction or regurgitation evidence. Change gdpr-erasure.md:17 to 'delete this corpus row / run artefacts'. Next release: add one sentence to the purge help/desc…
    Sources.

  • DOC-BLEGAL-024 (docs, High) — docs/usermanuals/en/compliance/gdpr.md: false claim — No matches found in masked data. / Because PII was masked at ingest, no specific person's data is recoverable from the model. The audit report co…
    severity High
    Where. docs/usermanuals/en/compliance/gdpr.md:75-78,85 (Turkish mirror: docs/usermanuals/tr/compliance/gdpr.md:78,85) · type: false claim
    The document says. No matches found in masked data. / Because PII was masked at ingest, no specific person's data is recoverable from the model. The audit report confirms this. / The masked JSONL doesn't contain their identifying info — already minimised.
    What the code does. Under GDPR Recital 26, pseudonymised data that can be attributed with additional information stays personal data, and EDPB Guidelines 01/2025 agree. Art. 15(1) and Art. 17(1) apply to all personal data the controller processes, not only to the masked training copy. EDPB Opinion 28/2024 says models trained with personal data 'cannot, in all cases, be considered anonymous' and that data may remain…
    Proposed correction. Search every store you control (raw documents, masked corpora, logs), not only the masked JSONL. Masking reduces but does not eliminate personal data, and it does not make a trained model anonymous (EDPB Opinion 28/2024); assess memorisation risk case by case.
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-BLEGAL-044 (docs, High) — site/features.html: misleading — GDPR Article 17 erasure — forgelm purge drops a data-subject's rows from training corpora and stamps the deletion in the append-only audit log.
    severity High
    Where. site/features.html:417-418 (Turkish mirror: site/js/translations.js:1140-1141) · type: misleading
    The document says. GDPR Article 17 erasure — forgelm purge drops a data-subject's rows from training corpora and stamps the deletion in the append-only audit log.
    What the code does. Art. 17(1) obliges erasure of the personal data concerning the subject. EDPB Opinion 28/2024 (adopted 2024-12-17) states that models trained on personal data 'cannot, in all cases, be considered anonymous' and that data may remain 'absorbed' in the parameters (para. 31). Supervisory authorities may order erasure of the dataset and/or the model (paras 114-115). purge removes corpus rows only. Labe…
    Proposed correction. Title 'GDPR Art. 17 corpus erasure'. Append to the body: 'Trained weights are not modified — assess whether the model must be retrained or withdrawn.' Fix translations.js:1035-1036 (EN), 1140-1141 (TR), 4480-4481 (DE), 4526-4527 (FR), 4572-4573 (ES) and 4618-4619 (ZH) too.

  • DOC-D3B02-005 (docs, High) — docs/usermanuals/en/compliance/gdpr-erasure.md: misleading — forgelm purge answers "Delete every record of me." The example is forgelm purge --row-id "ali@example.com" --corpus train.jsonl.
    severity High
    Where. docs/usermanuals/en/compliance/gdpr-erasure.md:17,27-28 (Turkish mirror: docs/usermanuals/tr/compliance/gdpr-erasure.md:17,27-28) · type: misleading
    The document says. forgelm purge answers "Delete every record of me." The example is forgelm purge --row-id "ali@example.com" --corpus train.jsonl.
    What the code does. Row mode deletes only rows whose id or row_id field equals the value. It does not search row content for the identifier. Reproduced: in a corpus with {"id":"ali@example.com"} and {"id":"r2","text":"mail ali@example.com"}, purge removed row 1, reported success, and left the email in row 2. Rows without an id key cannot be purged at all.
    Code references. forgelm/cli/subcommands/_purge.py:87, forgelm/cli/subcommands/_purge.py:272-295, forgelm/cli/subcommands/_purge.py:527-582
    Proposed correction. Change the table cell to: "Delete the row(s) with this id." Add: "--row-id matches the row's id/row_id field only, not text content. Run forgelm reverse-pii --query <identifier> first to find every row that mentions the subject, and purge each one by its id."
    Turkish mirror. the Turkish page has no such statement

  • DOC-D1B04-022 (docs, Medium) — docs/reference/audit_event_catalog.md: misleading — data.erasure_warning_memorisation fires when final_model/ exists 'for any run that consumed this corpus'.
    severity Medium
    Where. docs/reference/audit_event_catalog.md:80 (Turkish mirror: docs/reference/audit_event_catalog-tr.md:80) · type: misleading
    The document says. data.erasure_warning_memorisation fires when final_model/ exists 'for any run that consumed this corpus'.
    What the code does. The warning fires only when the literal directory <output_dir>/final_model exists. There is no corpus-consumption check, and training.final_model_dir is ignored. --output-dir defaults to the corpus' parent directory, so a model under checkpoints/ is not seen.
    Code references. forgelm/cli/subcommands/_purge.py:401-404, forgelm/cli/subcommands/_purge.py:713
    Proposed correction. 'Fires when <--output-dir>/final_model/ exists (literal name; --output-dir defaults to the corpus directory). It does not determine which runs consumed the corpus; point --output-dir at the training output directory.'
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-D3B02-021 (docs, Medium) — docs/usermanuals/en/compliance/gdpr-erasure.md: wrong detail — query_hash on data.access_request_query and target_id on data.erasure_* are byte-identical for the same subject.
    severity Medium
    Where. docs/usermanuals/en/compliance/gdpr-erasure.md:19 (Turkish mirror: docs/usermanuals/tr/compliance/gdpr-erasure.md:19) · type: wrong detail
    The document says. query_hash on data.access_request_query and target_id on data.erasure_* are byte-identical for the same subject.
    What the code does. They match only when both runs resolve the same salt. Two cases break it: FORGELM_AUDIT_SECRET is set for one run and not the other, or reverse-pii runs with --salt-source per_dir (which ignores the env secret) while purge, which has no salt-source flag, mixes it in.
    Code references. forgelm/cli/subcommands/_reverse_pii.py:251-268, forgelm/cli/subcommands/_purge.py:196-224
    Proposed correction. Add: "... byte-identical as long as both invocations use the same --output-dir and the same FORGELM_AUDIT_SECRET state (and reverse-pii is not run with --salt-source per_dir while the secret is set)."
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-D3B02-044 (docs, Low) — docs/usermanuals/en/compliance/gdpr-erasure.md: wrong detail — purge "atomically removes a row, a staging directory, or a compliance bundle".
    severity Low · already tracked: KNOWN-OPEN TRUST-02 docs/roadmap/phase-16-trust-surface-hardening.md:553-562
    Where. docs/usermanuals/en/compliance/gdpr-erasure.md:17 (Turkish mirror: docs/usermanuals/tr/compliance/gdpr-erasure.md:17) · type: wrong detail
    The document says. purge "atomically removes a row, a staging directory, or a compliance bundle".
    What the code does. Only row erasure is atomic (temp file plus os.replace). Run-scoped erasure deletes targets one by one with shutil.rmtree/unlink and can stop part-way ("Deletion failed mid-batch: N of M target(s) removed").
    Code references. forgelm/cli/subcommands/_purge.py:298-340, forgelm/cli/subcommands/_purge.py:908-930, forgelm/cli/subcommands/_purge.py:1092-1100
    Proposed correction. "atomically rewrites the corpus without the row, or deletes a staging directory or compliance files (run-scoped deletion is not atomic; a failure mid-batch is recorded in data.erasure_failed.files_modified)."
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-D3B02-048 (docs, Low) — docs/usermanuals/en/compliance/gdpr.md: wrong detail — "Detect memorisation: the audit step flags rows that overlap with known PII patterns."
    severity Low
    Where. docs/usermanuals/en/compliance/gdpr.md:94 (Turkish mirror: docs/usermanuals/tr/compliance/gdpr.md:94) · type: wrong detail
    The document says. "Detect memorisation: the audit step flags rows that overlap with known PII patterns."
    What the code does. forgelm audit reports PII counts per split and per type (pii_summary, pii_severity). It does not list or flag individual rows, and it does not detect memorisation.
    Code references. forgelm/data_audit/_aggregator.py:274-300
    Proposed correction. "The audit step counts PII matches per split and type (pii_summary, pii_severity), so you can see where personal data remains before training."
    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.
  • The release note names the contract this fix restores.

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.

Activity

  1. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    backlog: knownAlready tracked in docs/roadmap
    dependenciesDependency or ecosystem currency
    severity: highWrong behaviour or a false claim users rely on
    source: review-2026-10Found by the October 2026 code and documentation review
    urgency: nowTechnology currency: act now
    wave: 0Roadmap wave 0: immediate (patch release)
    on Oct 9, 2026
  2. added a commit that references this issue on Oct 23, 2026
    e6bf28d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: gdpr-approvalCode area: gdpr-approvalbacklog: knownAlready tracked in docs/roadmapbugSomething isn't workingdependenciesDependency or ecosystem currencydocumentationImprovements or additions to documentationseverity: highWrong behaviour or a false claim users rely onsource: review-2026-10Found by the October 2026 code and documentation reviewurgency: nowTechnology currency: act nowwave: 0Roadmap wave 0: immediate (patch release)

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions