Parent: #1936
Problem
resolveConvergedFeature's precedence for the safety feature (prompt-injection defanging) is: global env kill-switch (off → always false) → per-repo features.safety override (true/false) → allowlist default. This means a repo owner (or anyone with push access to a repo, since .gittensory.yml lives in the repo and only the file change itself is guardrailed for manual review, not the content of a safety-relevant features: block) can set features.safety: false in their own config, disabling defangReviewInput's prompt-injection defense for that repo's PR title/body/diff before it reaches the AI reviewer — even though the operator turned the safety feature on globally, intending it to apply everywhere. This is distinct from secret_leak, which is confirmed unconditional and cannot be disabled per-repo.
Failure scenario: an operator running self-hosted gittensory across many repos turns on the safety feature globally to protect every repo's AI reviewer from prompt-injection payloads embedded in a hostile PR title/body/diff. A repo owner (who may not be the operator in a multi-tenant self-host, or whose repo an attacker has push access to) sets features.safety: false in that repo's .gittensory.yml. A crafted PR title/body can now attempt to manipulate the AI reviewer's verdict without the defanging defense on that repo specifically, with no warning surfaced to the operator that a repo opted out of a protection they believed was instance-wide.
Requirements
- A security-critical feature the operator enables globally must not be silently overridable to "off" by a per-repo config a lower-trust actor controls.
- The fix should stay config-driven (this codebase's convention) — it should scope to security-relevant features specifically, not remove per-repo flexibility for non-security converged features (rag/reputation/unifiedComment).
Deliverables
- Invert precedence for
safety specifically so a per-repo override can only tighten (force-on) not loosen (force-off) an operator's global enablement — mirroring how secret_leak is already unconditional.
- At minimum (if inversion is judged too disruptive), emit a structured warning/
audit_events entry when a repo's manifest disables an operator-enabled safety-critical feature, so operators monitoring the fleet can detect the opt-out.
- Add a regression test: operator enables
safety globally, a repo's .gittensory.yml sets features.safety: false — assert the defense stays active (or, for the warning-only variant, assert the opt-out is recorded/surfaced).
Acceptance criteria
- An operator's global enablement of the safety feature can no longer be silently defeated by a single repo's config.
- Non-security converged features (rag/reputation/unifiedComment) retain their existing per-repo override flexibility, unaffected by this change.
Expected outcome
An operator's instance-wide prompt-injection defense actually applies instance-wide, closing a path where a lower-trust repo-level actor could quietly weaken a security control the operator explicitly turned on.
Parent: #1936
Problem
resolveConvergedFeature's precedence for thesafetyfeature (prompt-injection defanging) is: global env kill-switch (off → always false) → per-repofeatures.safetyoverride (true/false) → allowlist default. This means a repo owner (or anyone with push access to a repo, since.gittensory.ymllives in the repo and only the file change itself is guardrailed for manual review, not the content of a safety-relevantfeatures:block) can setfeatures.safety: falsein their own config, disablingdefangReviewInput's prompt-injection defense for that repo's PR title/body/diff before it reaches the AI reviewer — even though the operator turned the safety feature on globally, intending it to apply everywhere. This is distinct fromsecret_leak, which is confirmed unconditional and cannot be disabled per-repo.Failure scenario: an operator running self-hosted gittensory across many repos turns on the safety feature globally to protect every repo's AI reviewer from prompt-injection payloads embedded in a hostile PR title/body/diff. A repo owner (who may not be the operator in a multi-tenant self-host, or whose repo an attacker has push access to) sets
features.safety: falsein that repo's.gittensory.yml. A crafted PR title/body can now attempt to manipulate the AI reviewer's verdict without the defanging defense on that repo specifically, with no warning surfaced to the operator that a repo opted out of a protection they believed was instance-wide.Requirements
Deliverables
safetyspecifically so a per-repo override can only tighten (force-on) not loosen (force-off) an operator's global enablement — mirroring howsecret_leakis already unconditional.audit_eventsentry when a repo's manifest disables an operator-enabled safety-critical feature, so operators monitoring the fleet can detect the opt-out.safetyglobally, a repo's.gittensory.ymlsetsfeatures.safety: false— assert the defense stays active (or, for the warning-only variant, assert the opt-out is recorded/surfaced).Acceptance criteria
Expected outcome
An operator's instance-wide prompt-injection defense actually applies instance-wide, closing a path where a lower-trust repo-level actor could quietly weaken a security control the operator explicitly turned on.