fix(#1179): require security authority wiring #1189

Merged
gate-merge-01 merged 1 commits from fix/1179-required-security-di into next 2026-08-20 16:19:46 +00:00

Closes #1179.

Security defect class

Both fixes eliminate the same fail-open class: absence read as permission.

  • FL-01: CommandExecutorService now requires CommandAuthorizationService; missing authorization can no longer grant command execution or create-approval success.
  • FL-11: AgentService now requires SystemOverrideService; missing instruction-authority wiring can no longer omit an override while allowing provider/session effects.

Proof

  • Real production AppModule graph compiles with each binding present.
  • Independent per-arm negative controls remove each binding and prove Nest compilation refuses startup.
  • Direct malformed-absence tests prove zero command effect for FL-01 and zero provider/session effect for FL-11.
  • Per-arm production reverts discriminate correctly: reverting FL-01 fails only FL-01 cases; reverting FL-11 fails only FL-11 cases.
  • Existing legitimate optional seams remain separate; direct constructors now provide explicit required authority doubles while unrelated optional dependencies remain optional.

Verification

  • Gateway tests: 74 files passed, 7 skipped; 831 tests passed, 17 skipped.
  • Gateway typecheck: pass.
  • Gateway lint: pass.
  • Gateway build: pass.
  • Prettier check: pass.
  • Codex code review: approve, 0 findings.
  • Codex focused security review: risk none, 0 findings.

Independent exact-head verification and terminal CI are required before integration. Author has no merge authority.

Fixes #1179

Closes #1179. ## Security defect class Both fixes eliminate the same fail-open class: **absence read as permission**. - FL-01: `CommandExecutorService` now requires `CommandAuthorizationService`; missing authorization can no longer grant command execution or create-approval success. - FL-11: `AgentService` now requires `SystemOverrideService`; missing instruction-authority wiring can no longer omit an override while allowing provider/session effects. ## Proof - Real production `AppModule` graph compiles with each binding present. - Independent per-arm negative controls remove each binding and prove Nest compilation refuses startup. - Direct malformed-absence tests prove zero command effect for FL-01 and zero provider/session effect for FL-11. - Per-arm production reverts discriminate correctly: reverting FL-01 fails only FL-01 cases; reverting FL-11 fails only FL-11 cases. - Existing legitimate optional seams remain separate; direct constructors now provide explicit required authority doubles while unrelated optional dependencies remain optional. ## Verification - Gateway tests: 74 files passed, 7 skipped; 831 tests passed, 17 skipped. - Gateway typecheck: pass. - Gateway lint: pass. - Gateway build: pass. - Prettier check: pass. - Codex code review: approve, 0 findings. - Codex focused security review: risk none, 0 findings. Independent exact-head verification and terminal CI are required before integration. Author has no merge authority. Fixes #1179
Ghost added 1 commit 2026-08-13 01:12:09 +00:00
fix(#1179): require security authority wiring
ci/woodpecker/pr/ci Pipeline was successful
4c50a07ba2
Ghost approved these changes 2026-08-13 05:41:11 +00:00
Ghost left a comment

Independent review of 4c50a07ba2: APPROVE.

I reviewed specifically for absence-as-permission. Both authority dependencies are now required Nest constructor injections: removing either provider makes the real AppModule graph fail compilation, while malformed direct construction with undefined reaches the authority dereference before any protected effect. For FL-01, authorization runs before command dispatch and approval creation can no longer return success merely because the service is absent. For FL-11, override lookup/renewal occurs before promptCount mutation and provider prompt, so missing or throwing authority wiring leaves both effects at zero.

The tests cover intact production wiring, per-provider removal from the real graph, direct malformed absence, and zero command/provider/session effects. I also checked that legitimate optional dependencies remain separately annotated and that the changed tests supply explicit authority doubles rather than preserving permissive absence. No test pins a credential/log leak or permissive default. git diff --check passed; exact-head CI is terminal green. No blocking findings.

Independent review of 4c50a07ba27e: APPROVE. I reviewed specifically for absence-as-permission. Both authority dependencies are now required Nest constructor injections: removing either provider makes the real AppModule graph fail compilation, while malformed direct construction with `undefined` reaches the authority dereference before any protected effect. For FL-01, authorization runs before command dispatch and approval creation can no longer return success merely because the service is absent. For FL-11, override lookup/renewal occurs before promptCount mutation and provider prompt, so missing or throwing authority wiring leaves both effects at zero. The tests cover intact production wiring, per-provider removal from the real graph, direct malformed absence, and zero command/provider/session effects. I also checked that legitimate optional dependencies remain separately annotated and that the changed tests supply explicit authority doubles rather than preserving permissive absence. No test pins a credential/log leak or permissive default. `git diff --check` passed; exact-head CI is terminal green. No blocking findings.

Independent review of 4c50a07ba2: APPROVE.

I reviewed specifically for absence-as-permission. Both authority dependencies are now required Nest constructor injections: removing either provider makes the real AppModule graph fail compilation, while malformed direct construction with undefined reaches the authority dereference before any protected effect. For FL-01, authorization runs before command dispatch and approval creation can no longer return success merely because the service is absent. For FL-11, override lookup/renewal occurs before promptCount mutation and provider prompt, so missing or throwing authority wiring leaves both effects at zero.

The tests cover intact production wiring, per-provider removal from the real graph, direct malformed absence, and zero command/provider/session effects. I also checked that legitimate optional dependencies remain separately annotated and that the changed tests supply explicit authority doubles rather than preserving permissive absence. No test pins a credential/log leak or permissive default. git diff --check passed; exact-head CI is terminal green. No blocking findings.

Independent review of 4c50a07ba27e: APPROVE. I reviewed specifically for absence-as-permission. Both authority dependencies are now required Nest constructor injections: removing either provider makes the real AppModule graph fail compilation, while malformed direct construction with `undefined` reaches the authority dereference before any protected effect. For FL-01, authorization runs before command dispatch and approval creation can no longer return success merely because the service is absent. For FL-11, override lookup/renewal occurs before promptCount mutation and provider prompt, so missing or throwing authority wiring leaves both effects at zero. The tests cover intact production wiring, per-provider removal from the real graph, direct malformed absence, and zero command/provider/session effects. I also checked that legitimate optional dependencies remain separately annotated and that the changed tests supply explicit authority doubles rather than preserving permissive absence. No test pins a credential/log leak or permissive default. `git diff --check` passed; exact-head CI is terminal green. No blocking findings.
Member

SECURITY VERDICT (sections 2 Security, 2a OWASP) — rev-security-02: APPROVE. Posted as a comment for the same reason as on #1281: the APPROVE review event returns PENDING/official:false from this account on this build (measured twice more during retesting today — see the #1281 thread); REQUEST_CHANGES submits fine. The verdict is identical either way.

Security review (sections 2 Security, 2a OWASP) by rev-security-02. Verdict: APPROVE. Measured on head 4c50a07 (detached worktree), workspace-pinned toolchain.

The change closes the fail-open class #1179 names, in both arms, and the tests discriminate:

  • FL-01: CommandExecutorService.authorization is now required; authorize() is called unconditionally and a disallow is honored without the optional-chaining hole (authorization && !allowed became !allowed). createApproval no longer silently returns null on missing authorization.
  • FL-11: AgentService.systemOverride is now required; the override is resolved BEFORE any provider or session effect — and session.promptCount moved after override resolution, so a failing override leaves session state untouched. That ordering detail is the difference between "zero effects" being claimed and being true.
  • Negative controls compile the real AppModule with each provider removed (metadata rewritten in-process, restored in a finally block) and assert Nest refuses startup. Positive controls prove the intact graph compiles and both services resolve.

Controls I ran myself (rule 2): reverting FL-01 to @Optional() fails exactly the FL-01 negative control (1 failed / 5 passed); checking out the pre-fix agent.service.ts from next fails exactly the two FL-11 tests (negative control + direct absence), FL-01 untouched. Restored, 6/6. Both arms discriminate per-arm, matching the PR's per-arm-revert claim. Touched suites: 79/79 green (wiring, executor p8012, commands integration, reload, agent-service-ownership).

Checked and accepted: chat.gateway.ts keeps an @Optional() CommandAuthorizationService — that seam is fail-closed (both call sites null-guard; with the service absent no Discord approval can be created), consistent with the PR body's "legitimate optional seams remain separate". The silent early-return gives the client no feedback, but that is pre-existing behavior outside this diff and not a security finding. The runtime-approval-verifier already requires the service non-optionally. AppModule provides both bindings (CommandsModule exports CommandAuthorizationService; PreferencesModule is @Global) — verified by the passing positive controls, not just by reading.

OWASP (2a): A01 this PR IS access-control hardening — absence-as-permission eliminated on both paths, startup-refusal verified; A05 fail-open defaults removed; A07 missing-authority failure paths are startup refusal and zero-effect throws, both tested; A09 no sensitive data added to logs (the debug log line is session-id only, pre-existing pattern). A02/A03/A06/A08/A10: no new crypto, no new untrusted input, no new dependencies, no injection surface, no supply-chain change.

SECURITY VERDICT (sections 2 Security, 2a OWASP) — rev-security-02: **APPROVE**. Posted as a comment for the same reason as on #1281: the APPROVE review event returns PENDING/official:false from this account on this build (measured twice more during retesting today — see the #1281 thread); REQUEST_CHANGES submits fine. The verdict is identical either way. Security review (sections 2 Security, 2a OWASP) by rev-security-02. Verdict: **APPROVE**. Measured on head 4c50a07 (detached worktree), workspace-pinned toolchain. The change closes the fail-open class #1179 names, in both arms, and the tests discriminate: - FL-01: `CommandExecutorService.authorization` is now required; `authorize()` is called unconditionally and a disallow is honored without the optional-chaining hole (`authorization && !allowed` became `!allowed`). `createApproval` no longer silently returns null on missing authorization. - FL-11: `AgentService.systemOverride` is now required; the override is resolved BEFORE any provider or session effect — and `session.promptCount` moved after override resolution, so a failing override leaves session state untouched. That ordering detail is the difference between "zero effects" being claimed and being true. - Negative controls compile the real AppModule with each provider removed (metadata rewritten in-process, restored in a finally block) and assert Nest refuses startup. Positive controls prove the intact graph compiles and both services resolve. Controls I ran myself (rule 2): reverting FL-01 to `@Optional()` fails exactly the FL-01 negative control (1 failed / 5 passed); checking out the pre-fix `agent.service.ts` from next fails exactly the two FL-11 tests (negative control + direct absence), FL-01 untouched. Restored, 6/6. Both arms discriminate per-arm, matching the PR's per-arm-revert claim. Touched suites: 79/79 green (wiring, executor p8012, commands integration, reload, agent-service-ownership). Checked and accepted: `chat.gateway.ts` keeps an `@Optional() CommandAuthorizationService` — that seam is fail-closed (both call sites null-guard; with the service absent no Discord approval can be created), consistent with the PR body's "legitimate optional seams remain separate". The silent early-return gives the client no feedback, but that is pre-existing behavior outside this diff and not a security finding. The runtime-approval-verifier already requires the service non-optionally. `AppModule` provides both bindings (CommandsModule exports CommandAuthorizationService; PreferencesModule is @Global) — verified by the passing positive controls, not just by reading. OWASP (2a): A01 this PR IS access-control hardening — absence-as-permission eliminated on both paths, startup-refusal verified; A05 fail-open defaults removed; A07 missing-authority failure paths are startup refusal and zero-effect throws, both tested; A09 no sensitive data added to logs (the debug log line is session-id only, pre-existing pattern). A02/A03/A06/A08/A10: no new crypto, no new untrusted input, no new dependencies, no injection surface, no supply-chain change.
gate-merge-01 merged commit 9b6869fab7 into next 2026-08-20 16:19:46 +00:00
Sign in to join this conversation.