Updated by fix-consistency on 2026-09-06: - workplan status: ready → active Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
144 lines
5.6 KiB
Markdown
144 lines
5.6 KiB
Markdown
---
|
|
id: FLEX-WP-0024
|
|
type: workplan
|
|
title: "Sign the decision envelope: the response channel is unauthenticated"
|
|
domain: infotech
|
|
repo: flex-auth
|
|
status: active
|
|
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"
|
|
state_hub_workstream_id: "90577acd-6910-548d-a13e-1dbfdfb8ed27"
|
|
---
|
|
|
|
# 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
|
|
state_hub_task_id: "b8fad80d-5c44-5e26-a632-5096e0c0f3c5"
|
|
```
|
|
|
|
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
|
|
state_hub_task_id: "5482f3cd-36cb-55e0-8c52-0ca2340ed4d7"
|
|
```
|
|
|
|
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
|
|
state_hub_task_id: "041612ea-1be4-5997-8c32-49f8bcc50855"
|
|
```
|
|
|
|
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
|
|
state_hub_task_id: "82d6b75e-e2c6-5e3d-a897-fbdfb7c9094e"
|
|
```
|
|
|
|
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.
|