Skip to content

[Wave 3] Medium — Module sweep: Outbound integrations: HTTP chokepoint, webhooks, synthetic data, model card #565

Open

Description

@cemililik

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 · reachability default · intent contract-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 · reachability documented-opt-in · intent contract-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 · reachability default · intent contract-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 · reachability default · intent contract-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 · reachability documented-opt-in · intent intended-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 · reachability documented-opt-in · intent intended-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 · reachability default · intent intended-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 · reachability default · intent contract-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 · reachability default · intent contract-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 · reachability default · intent contract-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 · reachability default · intent contract-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 · reachability documented-opt-in · intent contract-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 · reachability default · intent contract-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.

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: integrationsCode area: integrationsbugSomething isn't workingsecuritySecurity-relevantseverity: mediumReal defect with limited reach or a workaroundsource: review-2026-10Found by the October 2026 code and documentation reviewwave: 3Roadmap wave 3: medium severity, module sweeps

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions