Repository navigation
[Wave 3] Medium — Module sweep: Outbound integrations: HTTP chokepoint, webhooks, synthetic data, model card #565
Description
Wave 3 · 13 finding(s) in 13 fix package(s). Fix these whenever a pull request touches this module ("clean the module you touch").
Findings
-
CR-A08-007 (code, Medium) — Non-RequestException errors (ValueError from URL parse/port, OSError from a missing CA bundle) escape safe_post/safe_get and WebhookNotifier, aborting training at notify_start or turning a completed…
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. An operator mistypes a port or bracket in webhook.url (or in the env var named by url_env). The run aborts at notify_start with exit 2 and no pipeline.failed event. If notify_on_start is false, a run that passed every gate and was saved instead exits 2, its Art. 12 log claims both completion and failure, and the CI pipeline treats a good model as a failed run. With require_human_approval, exit 4 becomes 2 after human_approval.required was already logged.
Where.forgelm/webhook.py:156-229,forgelm/webhook.py:287,forgelm/_http.py:316,forgelm/trainer.py:1686-1707(outer except that appends pipeline.failed)
Recommended fix. Catch ValueError, or a broad best-effort Exception, in _post_payload and around the urlparse in send. Validate webhook.url at config load. Move pipeline.completed after notify_success, or wrap the trainer's notify* calls the way the failure path already does (trainer.py:1705-1708). -
CR-A08-009 (code, Medium) — Local synthetic teacher re-tokenizes chat-template text with add_special_tokens=True (double BOS)
severity Medium · reachabilitydocumented-opt-in· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. Distilling from a Llama-3 teacher conditions every generation on a malformed prompt with a duplicated BOS, which quietly lowers the quality of the synthetic dataset.
Where.forgelm/synthetic.py:270-275,forgelm/synthetic.py:263-268,forgelm/inference.py:177
Recommended fix. Use apply_chat_template(..., tokenize=True, return_dict=True), or call tokenizer(formatted, add_special_tokens=False). Omit temperature when do_sample is False. -
CR-A08-011 (code, Medium) — Documentation promises model-card content and a filename the code does not produce
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · not reproduced (code trace)
Failure scenario. Operators expect an Ollama snippet and a license line that never appear, and auditors look for model_card.md.
Where.docs/usermanuals/en/deployment/gguf-export.md:137,docs/usermanuals/tr/deployment/gguf-export.md:138,docs/usermanuals/en/compliance/model-card.md:132,docs/reference/compliance_summary.md:54
Recommended fix. Delete the Ollama-snippet claim and the license tip, and rename the cited artefact to final_model/README.md, in both EN and TR. -
CR-A08-013 (code, Medium) — safe_get policy gates (SSRF block, http:// refusal, scheme, method allowlist, timeout floor) have no test; forgelm doctor tests mock safe_get itself
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Where.forgelm/_http.py:675-685,forgelm/_http.py:693-694,forgelm/_http.py:697-699,forgelm/_http.py:707-711
Recommended fix. Mirror TestSafePostHttpDiscipline for safe_get: parametrised private/loopback/CGNAT/IMDS literals raise HttpSafetyError with 'Private/loopback/IMDS'; http:// raises unless allow_insecure_http; ftp:// raises 'Unsupported URL scheme'; method='POST' raises; timeout below floor raises; ca_bundle/verify forwarded. -
CR-A08-016 (code, Medium) — Synthetic API teacher cannot reach http://, loopback or private endpoints although docs and docstring advertise local/self-hosted vLLM
severity Medium · reachabilitydocumented-opt-in· intentintended-but-harmful· confidence confirmed · independently confirmed · reproduced end to end
Where.forgelm/synthetic.py:60-72,forgelm/synthetic.py:66-72,forgelm/synthetic.py:240-245,forgelm/config.py:1329-1374
Recommended fix. Add opt-in synthetic.allow_private_destinations (and require_https/tls_ca_bundle) wired to safe_post, mirroring WebhookConfig. Re-raise HttpSafetyError from _generate_one so a policy error aborts at the first prompt. Fix the docstring and manual if the restriction stays. -
CR-A08-017 (code, Medium) — Webhook delivery silently no-ops with no log in two reachable cases: approve/reject with an inline webhook.url, and an unset url_env
severity Medium · reachabilitydocumented-opt-in· intentintended-but-harmful· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. Reviewer approves a staged model. The on-call channel never sees the decision, and the operator log shows nothing to explain it.
Where.forgelm/webhook.py:275-277,forgelm/webhook.py:69-83,forgelm/compliance.py:41-42,forgelm/compliance.py:1602-1612
Recommended fix. Log at WARNING once when notify_* is gated on but no URL resolves (naming url_env), and have approve/reject say whether a webhook was attempted. Clarify in approve_subcommand.md that approve/reject require url_env. -
CR-A08-020 (code, Medium) — --generate-data never checks synthetic.output_file against its input paths: pointing it at seed_file or the training dataset truncates that input on the first success
severity Medium · reachabilitydefault· intentintended-but-harmful· confidence confirmed · independently confirmed · reproduced with stubs
Failure scenario. An operator sets output_file to the seed path to 'expand the seeds in place'. A transient API outage fails 30% of prompts, which are now permanently gone from the seed set. The CI retry exits 0 and feeds the teacher JSON-wrapped prompts, producing a corrupted corpus that is then trained on.
Where.forgelm/synthetic.py:104-106,forgelm/synthetic.py:127-133(confirmed),forgelm/synthetic.py:151,forgelm/synthetic.py:177-178
Recommended fix. Refuse (exit 1) when realpath(output_file) equals realpath(seed_file), or when output_file already exists, unless an explicit overwrite/append opt-in is set. Write to a temp file and os.replace at the end, keeping the per-row flush to the temp file. Fix the 'Output file not created' message so it does not claim the file is absent when it exists. -
CR-A08-021 (code, Medium) — QMS docs and the risk treatment plan rely on a FORGELM_OPERATOR identity inside webhook payloads, but no payload has an operator field
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. A deployer relies on the risk treatment plan's attribution control to tell, at the Slack/Teams receiver, who approved a model. The message carries no identity, so the treatment fails silently during an incident review.
Where.docs/qms/risk_treatment_plan.md:140,forgelm/webhook.py:297-316,forgelm/webhook.py:39-46,docs/qms/access_control.md:28
Recommended fix. Either add the operator identity to the payload (it is already resolved for the audit log) or remove the claim from R-09. -
CR-A08-022 (code, Medium) — Library caller with model.offline=True still contacts (and downloads from) the Hub: from_pretrained gets no local_files_only, only the CLI exports HF_OFFLINE
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. Air-gapped service imports forgelm and calls SyntheticDataGenerator(cfg).generate() or get_model_and_tokenizer(cfg) with model.offline=True and a Hub repo id (or a typo'd path). Instead of failing locally, the process contacts the configured endpoint (HF_ENDPOINT or huggingface.co), disclosing the repo id, and downloads and uses whatever is served.
Where.forgelm/synthetic.py:294-313,forgelm/cli/_config_load.py:50-57,forgelm/cli/_dispatch.py:243,forgelm/model.py:570-585
Recommended fix. Pass local_files_only=bool(model.offline) to every AutoTokenizer/AutoModel/AutoProcessor.from_pretrained in synthetic.py, model.py (and the other from_pretrained sites such as judge/inference), or export the three HF_OFFLINE variables from a library-level helper rather than only the CLI. Add a test that asserts local_files_only=True reaches from_pretrained when offline=True. -
CR-A08-023 (code, Medium) — Docs and the site claim the audit log records webhook dispatches and failures; webhook outcomes only go to stderr
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. An operator debugging missing Slack notifications greps audit_log.jsonl for webhook_failed, finds nothing, and wrongly concludes delivery succeeded.
Where.site/js/translations.js:1778,site/js/translations.js:1801,forgelm/webhook.py:102-245,docs/reference/audit_event_catalog.md:148
Recommended fix. Reword both strings, or add audit events (webhook.dispatch_failed with a masked target; data.synthetic_generated with teacher, revision, seed file hash and output hash) and catalogue them. -
CR-A08-025 (code, Medium) — Webhook free-text fields are scrubbed for credentials only; PII in failure reasons or reject comments reaches Slack/Teams verbatim
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. An approver runs 'forgelm reject --comment "model regurgitates jane.doe@example.com / +90 532..."'. The email and phone number land in a Slack channel that persists and indexes message bodies.
Where.forgelm/webhook.py:320-358,forgelm/webhook.py:513-530(confirmed),forgelm/trainer.py:1706,forgelm/cli/subcommands/_approve.py:733-736
Recommended fix. Also run the regex mask_pii over the outbound free-text fields in _mask_outbound_strings, or narrow the doc claim to 'no training rows; free-text reasons are credential-masked only'. -
CR-A08-027 (code, Medium) — Webhook free text is sent with no chat-markup escaping, and a TC-U judge-repo config reaches training.reverted end to end
severity Medium · reachabilitydocumented-opt-in· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. An operator configures evaluation.llm_judge.judge_model as a Hub repo (or a downloaded copy) whose publisher put a crafted model_type in config.json, with auto_revert: true and a Slack webhook or a Block Kit relay as the docs suggest. The judge fails to load, the run reverts, and the operator channel receives a ForgeLM notification whose body contains @channel/@here, a link labelled 'Approve model' pointing to an attacker URL, and a bold '✅ Training succeeded - all gates passed' line. Whether m…
Where.forgelm/webhook.py:297-306,forgelm/webhook.py:320-358,forgelm/webhook.py:489-511,forgelm/webhook.py:546-570
Recommended fix. Treat attachments[0].title/text as presentation built from untrusted text. Escape &, <, > as &, <, > (Slack's rule). Neutralise @everyone/@here, for example with a zero-width joiner. Send exception-derived reasons as an inert block: escape or strip backticks, or replace the text with exception type plus a fixed summary and keep the raw str(e) in the local log only. Keep the raw fields a… -
CR-A08-028 (code, Medium) — model-card.md says compliance.intended_purpose/known_limitations never reach the card, but the Configuration block publishes them with provider_contact, the DPIA/personal-data flags and the risk lists
severity Medium · reachabilitydefault· intentcontract-violation· confidence confirmed · independently confirmed · reproduced end to end
Failure scenario. Following :25, an operator puts internal-only limitations or purpose text and a named contact in compliance.*, sets personal_data_included: true with dpia_completed: false, and publishes final_model/ to the Hub. The card exposes all of it publicly.
Where.docs/usermanuals/en/compliance/model-card.md:19,docs/usermanuals/en/compliance/model-card.md:139,docs/usermanuals/tr/compliance/model-card.md:25,forgelm/model_card.py:296-297
Recommended fix. Correct :25 in EN and TR: the values appear verbatim in the Configuration block, but as YAML, not as card sections. List which blocks are published and warn that the card is intended for public Hub upload. Alternatively, exclude compliance/risk_assessment/data.governance from the card dump by default, or make their inclusion opt-in.
Done when
- Every finding above is fixed or closed with a reason (tick it here).
- Code fixes carry a test; doc fixes update the Turkish mirror in the same PR.
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