--- id: FLEX-WP-0024 type: workplan title: "Sign the decision envelope: the response channel is unauthenticated" domain: infotech repo: flex-auth status: ready owner: claude topic_slug: netkingdom planning_priority: P1 planning_order: 240 depends_on_workplans: - FLEX-WP-0021 related_workplans: - FLEX-WP-0019 - FLEX-WP-0023 created: "2026-09-06" updated: "2026-09-06" --- # FLEX-WP-0024 — Sign the decision envelope: the response channel is unauthenticated Opened by `FLEX-DEC-2026-010`, from a question `secrets-engine` asked rather than answering locally: **how is the response channel authenticated?** They declined to choose a transport control for flex-auth's service on the grounds that it is not a consumer's call. Correct, and it is why this is a flex-auth workplan and not a mitigation in their repo. The stance today: pins serve plain HTTP, `flex-auth.decision-record.v1` carries no signature, and a responder that knows the package id and version — both published — can return a well-formed `effect: allow` that passes every check a consumer performs. ## The two things not to get backwards **The digests do not authenticate the responder and cannot.** Every input to them is either sent by the caller or published, so a forger reproduces `request_digest`, `policy_package_digest`, and `registry_snapshot_digest` exactly. Publishing more digests makes a forged envelope look *more* authenticated, not less. **Fail-closed does not cover this.** It protects against a PDP that is absent, not against one that lies. An unreachable PDP denies; a lying PDP allows. ## 1. Publish the stance and stop publishing bare Service names ```task id: FLEX-WP-0024-T01 status: done priority: high ``` Owner: `flex-auth`. **Done 2026-09-06.** `docs/decision-record-contract.md` states that digest recomputation is not responder verification, because the contract is the only document a consumer is guaranteed to read. `docs/operator-caller-access-path.md` carries the reproduced DNS probe and the channel analysis. The trailing-dot FQDN replaced the bare Service name in `examples/secrets-engine/README.md`, `SCOPE.md`, and the `FLEX-WP-0021-T05` handover note — a bare `*.svc.cluster.local` name resolves through `search ad.binect.de` to one unrelated public host, and a name for a service that does not exist resolves to the same address. ## 2. Choose the signature shape and key custody ```task id: FLEX-WP-0024-T02 status: todo priority: high ``` Owner: `flex-auth`; key custody is **not** ours to invent. - Detached signature over the canonical envelope, preferred over transport-only mTLS for two reasons: it survives the transport, so a decision audited later is still attributable, where mTLS authenticates a connection that no longer exists; and it composes with `FLEX-DEC-2026-009`, since a record that names its authenticated caller *and* carries flex-auth's signature is an audit artifact where either alone is a claim. - Decide the canonical form to sign. The existing canonical-JSON rules from `docs/canonical-request-digest.md` are the obvious basis, but the signed material is the **envelope**, not the request, and it must exclude the signature field itself. Write it down before implementing: an implicit canonicalisation rule is what `FLEX-DEC-2026-007` had to correct. - **Do not mint a key in this repo.** Route custody via `warden route find "decision envelope signing key"` and OpenBao, per the estate credential-routing rule. flex-auth decides the shape; it does not own key material. - State key rotation and how a consumer discovers the current public key, since a consumer that cannot rotate trust has a pinned key that will one day be wrong. Gate: the canonical form, the algorithm, and the custody path are written down and reviewed before any signing code exists. ## 3. Implement signing and verification ```task id: FLEX-WP-0024-T03 status: wait priority: high ``` Owner: `flex-auth`. - Add the signature to `schemas/decision_envelope.schema.json` as additive, and state in the contract that `request_digest` is unaffected so no consumer re-pins — the same note that was needed for v2 and for `approval_binding_digest`. - Emit unsigned when no key is configured, and say so in the envelope rather than omitting the field silently. Absence stated, not implied — the `pdp_digest` precedent. - Ship a verification example and a fixture pair in `examples/secrets-engine/replay/`: a genuine signed envelope and one whose payload was altered after signing, so a consumer's verifier is tested against a real failure rather than only a real success. **A verifier that has never seen an invalid signature is untested**, which is the lesson from every fixture suite in `FLEX-DEC-2026-008`. Gate: an altered envelope fails verification, an unaltered one passes, and a consumer can perform both from the fixtures alone. ## 4. Report the gap to gate-house ```task id: FLEX-WP-0024-T04 status: wait priority: medium ``` Owner: `flex-auth`. §6.4 obligates the PEP to act on a decision and §9.3 splits degraded mode by owner, but **nothing in the standard requires a PDP's response to be authenticable**. So a fully conformant PEP can trust a forged allow while meeting every published obligation. That is a gap in the standard, not only in this implementation, and it belongs in the outstanding v0.8 assent round alongside the `FLEX-DEC-2026-009` finding.