Grade every catalog lane; record ADR-0007
Closes the RISK-F-0003 exposure. All 14 ungraded lanes now carry an explicit risk grade with its justification in the entry: 17 high, 10 standard, 0 ungraded. The agent read-boundary now fires (exit 7) on lanes that were silently outside it. Graded on merit rather than defensively. A first pass marked two ordinary internal workload secrets high; test_high_risk_lanes_classified asserted the opposite and was right, so both were regraded down. high means disclosure into a logged context is damaging beyond what rotation recovers. inter-hub-bootstrap-ssh is high conservatively, with the reason in the entry so it is regraded with evidence rather than assumed down. ADR-0007 records the rule the grading rests on: build-stage permissiveness applies to controls that gate work, not to controls that prevent credential disclosure. The test is friction, not severity — the read-boundary blocks nobody, since --out/--exec/--wrap remain available, so relaxing it buys nothing. A blocked operation is recovered by retrying; a credential in a logged transcript is not recovered by rotation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7b8dd3467b
commit
ac85259c20
7 changed files with 158 additions and 1 deletions
|
|
@ -14,6 +14,7 @@ distinction that matters here.
|
|||
| `ADR-0004` | High-risk lanes refuse raw value streaming to agent sessions |
|
||||
| `ADR-0005` | Implement one lane narrowly, route everything else |
|
||||
| `ADR-0006` | Enforcement is zone-scoped, never a global flag |
|
||||
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade |
|
||||
|
||||
### Owned versus inherited — check `owner:` before changing anything
|
||||
|
||||
|
|
|
|||
1
SCOPE.md
1
SCOPE.md
|
|
@ -154,6 +154,7 @@ superseding ADR, never an in-place edit.
|
|||
| `ADR-0004` | High-risk lanes refuse raw value streaming to agent sessions |
|
||||
| `ADR-0005` | Implement one lane narrowly, route everything else |
|
||||
| `ADR-0006` | Enforcement is zone-scoped, never a global flag (`policy.enabled` stays off until WP-0032) |
|
||||
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade |
|
||||
|
||||
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
|
||||
credential-management standard, the-custodian's ADR-001 workplan convention — are
|
||||
|
|
|
|||
|
|
@ -119,3 +119,5 @@
|
|||
| task | WARDEN-WP-0032-T02 | wait | — | workplans/WARDEN-WP-0032-security-zones.md |
|
||||
| task | WARDEN-WP-0032-T03 | wait | — | workplans/WARDEN-WP-0032-security-zones.md |
|
||||
| task | WARDEN-WP-0032-T04 | wait | — | workplans/WARDEN-WP-0032-security-zones.md |
|
||||
| task | WARDEN-WP-0032-T05 | todo | — | workplans/WARDEN-WP-0032-security-zones.md |
|
||||
| task | WARDEN-WP-0032-T06 | wait | — | workplans/WARDEN-WP-0032-security-zones.md |
|
||||
|
|
|
|||
100
docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md
Normal file
100
docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
id: ops-warden-adr-0007
|
||||
type: adr
|
||||
title: "ADR-0007 — Build-stage permissiveness stops at credential disclosure"
|
||||
domain: infotech
|
||||
repo: ops-warden
|
||||
status: accepted
|
||||
version: "1.0"
|
||||
revision: "1"
|
||||
owner: ops-warden
|
||||
binds: "ops-warden"
|
||||
created: "2026-08-19"
|
||||
updated: "2026-08-19"
|
||||
last_reviewed: "2026-08-19"
|
||||
review_interval: 6m
|
||||
enforced_by: "registry/routing/catalog.yaml risk grades; src/warden/cli.py agent read-boundary; WARDEN-WP-0032-T06"
|
||||
supersedes: ""
|
||||
successor: ""
|
||||
---
|
||||
|
||||
# ADR-0007 — Build-stage permissiveness stops at credential disclosure
|
||||
|
||||
## Status
|
||||
|
||||
Accepted 2026-08-19, alongside grading the last 14 ungraded catalog lanes.
|
||||
|
||||
## Context
|
||||
|
||||
`ADR-0006` deferred a global fail-closed authorization gate because uniform
|
||||
enforcement across an estate under deep refactor hardens the access needed to
|
||||
perform the refactor. The organization's declared posture is `build`
|
||||
(WP-0029), and the operator has confirmed the estate need not be tight yet.
|
||||
|
||||
That is correct, and it is also the kind of principle that quietly generalises
|
||||
past its warrant. Read loosely, "we are in build stage" argues for relaxing
|
||||
every control, including the ones that stop a credential landing in a logged
|
||||
agent transcript. Those are not the same class of control, and the difference
|
||||
is not severity — it is **cost**.
|
||||
|
||||
`RISK-F-0003` made the distinction concrete. `ADR-0004` reads as a categorical
|
||||
rule: high-risk lanes refuse raw value streaming to agent sessions. The
|
||||
implementation was `risk == "high"` against an **optional** field, so 14 of 27
|
||||
lanes never reached the control at all — five of them `exec_capable`. The
|
||||
control had not been relaxed by anyone's decision. It had simply never been
|
||||
reached, which is worse, because nothing announced it.
|
||||
|
||||
## Decision
|
||||
|
||||
**Build-stage permissiveness applies to controls that gate work. It does not
|
||||
apply to controls that prevent credential disclosure.**
|
||||
|
||||
The test is friction, not severity:
|
||||
|
||||
- A control that can **block a legitimate operation** — a fail-closed
|
||||
authorization gate, an enforcement stance — is a candidate for relaxation
|
||||
while the organization is in `build`, and `ADR-0006` scopes that relaxation
|
||||
to zones.
|
||||
- A control that **redirects how a value moves without preventing the work** —
|
||||
the agent read-boundary, which refuses raw stdout but leaves `--out`,
|
||||
`--exec`, `--wrap` and `--fingerprint` fully available — is not relaxed by
|
||||
build posture, because relaxing it buys nothing. Nobody is unblocked by it.
|
||||
|
||||
The asymmetry that settles it: a blocked operation is recovered by retrying.
|
||||
A credential written into a logged transcript is not recovered by rotation —
|
||||
rotation limits the damage, it does not unwrite the log. The 2026-07-16
|
||||
disclosure is the case in point.
|
||||
|
||||
**Therefore, regardless of `organization_posture`:**
|
||||
|
||||
1. Every catalog lane carries an explicit `risk` grade. **Absence is not a
|
||||
grade**, and a lane that omits it is a defect, not a default.
|
||||
2. Grading is done on merit, per lane. This decision is not licence to grade
|
||||
everything `high` — an over-broad grade is its own inaccuracy, and
|
||||
`tenancy-posture` §6's *accuracy, not altitude* applies to this field too.
|
||||
3. Minimum credential-handling standards — the read-boundary, the safe fetch
|
||||
transports, the no-secret audit guard — hold in every posture.
|
||||
|
||||
## Consequences
|
||||
|
||||
**We accept** the grading cost, now and on every new lane. That is the point:
|
||||
`WARDEN-WP-0032-T06` makes an ungraded lane impossible rather than merely
|
||||
discouraged, because a rule enforced by remembering is not enforced.
|
||||
|
||||
**We reject** "build stage" as a general argument in credential-handling
|
||||
discussions. It is a real and useful argument about *gating*, and citing it
|
||||
against a disclosure control is a category error this record exists to name.
|
||||
|
||||
**We note what this decision is not.** It does not set severity for
|
||||
`RISK-F-0003` — that is `risk-nexus`'s. It does not make ops-warden the judge of
|
||||
other repos' controls. And it does not survive contact with a zone model that
|
||||
says otherwise: when `zone-engine` defines admission standards, a zone may
|
||||
legitimately require *more* than this floor. It may not require less.
|
||||
|
||||
## Related
|
||||
|
||||
- `ADR-0004` — high-risk lanes refuse raw value streaming to agent sessions
|
||||
- `ADR-0006` — enforcement is zone-scoped, never a global flag
|
||||
- `RISK-F-0003` — the read-boundary blind spot that prompted this
|
||||
- `WARDEN-WP-0032-T05` / `T06` — the grading, and making absence impossible
|
||||
- `zone-engine` `ZONE-WP-0001` — where admission standards will be defined
|
||||
|
|
@ -68,3 +68,4 @@ and the publication is a defect.
|
|||
| `ADR-0004` | High-risk lanes refuse raw value streaming to agent sessions | ops-warden, and any agent runtime calling `warden access` |
|
||||
| `ADR-0005` | Implement one lane narrowly, route everything else | ops-warden |
|
||||
| `ADR-0006` | Enforcement is zone-scoped, never a global flag | ops-warden |
|
||||
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure | ops-warden |
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ version: 1
|
|||
entries:
|
||||
- id: ssh-cert-host-access
|
||||
title: Short-lived SSH certificate for host / ops reachability
|
||||
# Emits a signed certificate — a public artifact. The private key never leaves the caller (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [ssh, certificate, cert, host, access, sign, adm, agt, atm, reachability, ops]
|
||||
owner_repo: ops-warden
|
||||
subsystem: ops-warden
|
||||
|
|
@ -57,6 +59,8 @@ entries:
|
|||
|
||||
- id: ops-warden-warden-sign-token
|
||||
title: Scoped OpenBao token for ops-warden SSH signing (warden-sign)
|
||||
# A scoped VAULT_TOKEN is a credential in its own right. Graded on what the value is, not on whether ops-warden currently proxies it (WARDEN-WP-0032-T05).
|
||||
risk: high
|
||||
need_keywords: [vault_token, vault, token, warden-sign, warden, ops-warden, signing, sign, smoke, flex-auth, credential, broker, lease, openbao, ssh, production]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao credential broker
|
||||
|
|
@ -90,6 +94,8 @@ entries:
|
|||
|
||||
- id: openbao-api-key
|
||||
title: API key, DB credential, or dynamic lease
|
||||
# Wildcard lane over platform/workloads/<domain>/<workload>/<bundle>: its ceiling is the most dangerous bundle it can resolve to (WARDEN-WP-0032-T05).
|
||||
risk: high
|
||||
need_keywords: [api, key, secret, database, db, password, token, lease, openbao, vault, kv, dynamic, credential, npm, npm_auth_token, registry]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao
|
||||
|
|
@ -123,6 +129,8 @@ entries:
|
|||
|
||||
- id: whynot-design-npm-publish
|
||||
title: whynot-design npm publish token (@whynot/design → coulomb Gitea registry)
|
||||
# Publish rights to the package registry — a leaked token is a supply-chain write, not a read (WARDEN-WP-0032-T05).
|
||||
risk: high
|
||||
need_keywords: [whynot-design, whynot, npm, publish, npm_auth_token, gitea, registry, coulomb, package]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao
|
||||
|
|
@ -164,6 +172,8 @@ entries:
|
|||
|
||||
- id: flex-auth-policy-check
|
||||
title: Authorization decision — may this actor perform this action
|
||||
# Returns an authorization decision; no credential flows (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [authorization, policy, permission, allow, deny, may, flex-auth, topaz, pdp, decision]
|
||||
owner_repo: flex-auth
|
||||
subsystem: flex-auth
|
||||
|
|
@ -179,6 +189,8 @@ entries:
|
|||
|
||||
- id: key-cape-oidc-login
|
||||
title: Interactive login, OIDC token, or MFA
|
||||
# Interactive browser OIDC: a login flow, not a KV read. No stored value is fetched, and warden access already excludes is_login from raw-value streaming (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [login, oidc, identity, mfa, token, jwt, sso, keycloak, key-cape, iam, claims, authenticate, signin]
|
||||
owner_repo: key-cape
|
||||
subsystem: key-cape / Keycloak
|
||||
|
|
@ -203,6 +215,8 @@ entries:
|
|||
|
||||
- id: ops-bridge-tunnel
|
||||
title: SSH tunnel or port forward
|
||||
# Routes to ops-bridge and supplies a cert_command; no secret value flows (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [tunnel, port, forward, bridge, ops-bridge, reverse, transport, ssh-tunnel, cert_command]
|
||||
owner_repo: ops-bridge
|
||||
subsystem: ops-bridge
|
||||
|
|
@ -218,6 +232,8 @@ entries:
|
|||
|
||||
- id: railiance-infra-principals
|
||||
title: Host SSH principal file or force-command deployment
|
||||
# Principal-file deployment via Ansible; no secret value flows (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [principal, auth_principals, force-command, host, sshd, hardening, railiance-infra, ansible]
|
||||
owner_repo: railiance-infra
|
||||
subsystem: railiance-infra
|
||||
|
|
@ -233,6 +249,8 @@ entries:
|
|||
|
||||
- id: inter-hub-bootstrap-ssh
|
||||
title: Inter-Hub bootstrap SSH envelope
|
||||
# Graded high conservatively: ops-warden could not establish from the lane definition that no key material moves in the envelope. Regrade with evidence, do not assume down (WARDEN-WP-0032-T05).
|
||||
risk: high
|
||||
need_keywords: [inter-hub, interhub, bootstrap, ops-hub, agt-interhub-bootstrap, envelope, force-command, CUST-WP-0049]
|
||||
owner_repo: ops-warden
|
||||
subsystem: ops-warden + railiance-infra
|
||||
|
|
@ -248,6 +266,8 @@ entries:
|
|||
|
||||
- id: activity-core-issue-sink
|
||||
title: activity-core IssueSink → issue-core REST emission
|
||||
# Emission routing only — the API key is a separate lane (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [activity-core, issue-sink, issue-core, emission, issue_core_url, issue_core_api_key, tasks, ingest, rest, issuesink]
|
||||
owner_repo: activity-core
|
||||
subsystem: activity-core + issue-core
|
||||
|
|
@ -263,6 +283,9 @@ entries:
|
|||
|
||||
- id: issue-core-ingestion-api-key
|
||||
title: issue-core ingestion API key (OpenBao KV + ESO)
|
||||
# Ordinary internal workload secret: an ingestion key for a first-party
|
||||
# service. Rotatable, no spend, no tenant data, no admin scope (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [issue-core, ingestion, api, key, openbao, issue_core_api_key, eso, external-secrets]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao + issue-core + activity-core
|
||||
|
|
@ -300,6 +323,9 @@ entries:
|
|||
|
||||
- id: reuse-surface-hub-write-token
|
||||
title: reuse-surface federation hub write bearer token
|
||||
# Ordinary internal workload secret. Write access to a first-party federation
|
||||
# surface; damaging to forge, but rotatable and internal (WARDEN-WP-0032-T05).
|
||||
risk: standard
|
||||
need_keywords: [reuse-surface, reuse_surface, hub, register, federation, write, token, bearer, REUSE_SURFACE_TOKEN, REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET, reuse.coulomb.social]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao + reuse-surface
|
||||
|
|
@ -635,6 +661,8 @@ entries:
|
|||
|
||||
- id: object-storage-sts
|
||||
title: Object-storage STS / temporary S3 credentials
|
||||
# Temporary S3 credentials are still credentials (WARDEN-WP-0032-T05).
|
||||
risk: high
|
||||
need_keywords: [s3, sts, object-storage, minio, artifact-store, temporary, credentials, bucket, vending]
|
||||
owner_repo: net-kingdom
|
||||
subsystem: flex-auth + OpenBao + artifact-store
|
||||
|
|
@ -760,6 +788,8 @@ entries:
|
|||
|
||||
- id: audit-core-senders
|
||||
title: audit-core sender registry (write and operator-read tokens)
|
||||
# Vends write and operator-read tokens (WARDEN-WP-0032-T05).
|
||||
risk: high
|
||||
need_keywords: [audit-core, senders, sender registry, ingest token, AUDIT_CORE_SENDERS]
|
||||
owner_repo: ops-mason
|
||||
subsystem: OpenBao + audit-core
|
||||
|
|
|
|||
|
|
@ -175,8 +175,9 @@ owning it — a superseding record, never an in-place edit. Update `SCOPE.md`,
|
|||
|
||||
```task
|
||||
id: WARDEN-WP-0032-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "4d5d2756-c880-4e90-8fb7-1f03674a4cd9"
|
||||
```
|
||||
|
||||
**Grade the five exposed lanes now — do not wait for the model.**
|
||||
|
|
@ -196,10 +197,31 @@ having them inferred. Verify separately whether OpenBao's
|
|||
`agent-high-risk-boundary` policy covers these paths; `RISK-F-0003` deliberately
|
||||
does not assume it does, because those paths were graded by the same omission.
|
||||
|
||||
**Done 2026-08-19, operator-sanctioned.** All 14 ungraded lanes graded on merit,
|
||||
each with its justification in the entry: 17 `high`, 10 `standard`, **0
|
||||
ungraded**. `warden access <lane> --fetch` with `WARDEN_AGENT_ID` set now exits 7
|
||||
on lanes that were silently outside the control an hour earlier.
|
||||
|
||||
Graded on merit, not defensively. A first pass marked
|
||||
`issue-core-ingestion-api-key` and `reuse-surface-hub-write-token` `high`; the
|
||||
existing test `test_high_risk_lanes_classified` asserted the opposite and was
|
||||
right — ordinary internal workload secrets are `standard`. Both were regraded
|
||||
down. `high` means disclosure into a logged context is damaging beyond what
|
||||
rotation recovers: provider keys with spend, admin PATs, tenant commercial data,
|
||||
supply-chain publish rights. `inter-hub-bootstrap-ssh` is `high` **conservatively**
|
||||
— ops-warden could not establish that no key material moves in the envelope, and
|
||||
that is recorded in the entry so it is regraded with evidence rather than assumed
|
||||
down.
|
||||
|
||||
The rule is now `ADR-0007`: build-stage permissiveness stops at credential
|
||||
disclosure. Not yet verified: whether OpenBao's `agent-high-risk-boundary`
|
||||
policy covers these paths (T06).
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0032-T06
|
||||
status: wait
|
||||
priority: medium
|
||||
state_hub_task_id: "8c082416-0ff9-45dc-8cbd-e9ca7913a6ef"
|
||||
```
|
||||
|
||||
**Make absence impossible, once the model says what absence means.** Waits on
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue