Finish coding-agent high-risk boundary coverage
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
parent
7a1dcb8a52
commit
429cc912ed
12 changed files with 720 additions and 54 deletions
5
Makefile
5
Makefile
|
|
@ -340,6 +340,9 @@ credential-change-lifecycle-plan: ## Render deactivation/rotation/compromise lif
|
|||
credential-change-lifecycle-event: ## Record lifecycle event; pass CREDENTIAL_CHANGE_LIFECYCLE_ARGS
|
||||
scripts/credential-change.py lifecycle-event $(CREDENTIAL_CHANGE) --action $(CREDENTIAL_CHANGE_LIFECYCLE_ACTION) $(CREDENTIAL_CHANGE_LIFECYCLE_ARGS)
|
||||
|
||||
agent-high-risk-boundary-check: ## Check policy against the versioned ops-warden high-risk path input
|
||||
python3 scripts/agent_high_risk_boundary.py --json
|
||||
|
||||
credential-change-import-inventory: ## Import existing lane as non-secret CCR; pass CREDENTIAL_CHANGE_IMPORT_ARGS
|
||||
scripts/credential-change.py import-inventory $(CREDENTIAL_CHANGE_IMPORT_ARGS)
|
||||
|
||||
|
|
@ -464,4 +467,4 @@ help: ## Show this help
|
|||
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
|
||||
|
||||
.PHONY: apps-pg-isolation-probe
|
||||
.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables apps-pg-evidence consumption-preflight help
|
||||
.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory agent-high-risk-boundary-check openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables apps-pg-evidence consumption-preflight help
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
| workplan | RAILIANCE-WP-0016 | finished | — | workplans/RAILIANCE-WP-0016-apps-pg-resource-evidence.md |
|
||||
| workplan | RAILIANCE-WP-0016 | finished | — | workplans/RAILIANCE-WP-0016-architecture-cleanup-backlog.md |
|
||||
| workplan | RAILIANCE-WP-0017 | finished | — | workplans/RAILIANCE-WP-0017-consumption-mode-enforcement.md |
|
||||
| workplan | RAILIANCE-WP-0022 | blocked | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| workplan | RAILIANCE-WP-0022 | finished | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| workplan | RAILIANCE-WP-0023 | finished | — | workplans/RAILIANCE-WP-0023-hub-core-candidate-credential-lanes.md |
|
||||
| workplan | RPF-WP-0018 | finished | — | workplans/RPF-WP-0018-policy-surface-alignment.md |
|
||||
| workplan | RPF-WP-0019 | finished | — | workplans/RPF-WP-0019-apps-pg-recoverability-and-controls.md |
|
||||
|
|
@ -91,7 +91,8 @@
|
|||
| task | RAILIANCE-WP-0022-T02 | done | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| task | RAILIANCE-WP-0022-T03 | done | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| task | RAILIANCE-WP-0022-T04 | done | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| task | RAILIANCE-WP-0022-T05 | wait | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| task | RAILIANCE-WP-0022-T05 | done | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| task | RAILIANCE-WP-0022-T06 | done | — | workplans/RAILIANCE-WP-0022-agent-high-risk-boundary-coverage.md |
|
||||
| task | RAILIANCE-WP-0023-T01 | done | — | workplans/RAILIANCE-WP-0023-hub-core-candidate-credential-lanes.md |
|
||||
| task | RAILIANCE-WP-0023-T02 | done | — | workplans/RAILIANCE-WP-0023-hub-core-candidate-credential-lanes.md |
|
||||
| task | RAILIANCE-WP-0023-T03 | done | — | workplans/RAILIANCE-WP-0023-hub-core-candidate-credential-lanes.md |
|
||||
|
|
|
|||
117
docs/coding-agent-openbao-identity.md
Normal file
117
docs/coding-agent-openbao-identity.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# Coding-agent OpenBao identity
|
||||
|
||||
`RAILIANCE-WP-0022` requires an OpenBao identity that is distinct from the
|
||||
attended human `platform-admin` role. The identity is split across its proper
|
||||
owners:
|
||||
|
||||
- KeyCape owns a confidential `client_credentials` service principal and its
|
||||
signed short-lived JWT.
|
||||
- railiance-platform owns the exact-bound OpenBao JWT role and the policies on
|
||||
the resulting OpenBao token.
|
||||
- ops-warden remains an interested consumer and SSH-certificate issuer; it does
|
||||
not issue this identity or its credentials.
|
||||
|
||||
The operational identity is the platform-owned AppRole
|
||||
`coding-agent-railiance-platform`. The exact-bound KeyCape JWT role is the
|
||||
issuer-backed migration target; it does not block the deny-wins control while
|
||||
the KeyCape client is being registered.
|
||||
|
||||
## Operational AppRole
|
||||
|
||||
The reviewed role body is `openbao/auth/coding-agent-approle.json`. Apply it
|
||||
with attended platform authority:
|
||||
|
||||
```bash
|
||||
bao write auth/approle/role/coding-agent-railiance-platform \
|
||||
@openbao/auth/coding-agent-approle.json
|
||||
```
|
||||
|
||||
This is a standing machine role, not a manually minted token. Every SecretID
|
||||
expires after five minutes and is single-use; resulting tokens last at most 15
|
||||
minutes, have eight uses, omit the default policy, and always carry
|
||||
`agent-high-risk-boundary`. The role also carries the otherwise-readable
|
||||
issue-core policy solely to make the deny-wins invariant directly testable.
|
||||
The boundary permits token metadata lookup, capabilities inspection, and
|
||||
self-revocation; it does not grant token creation or management of any other
|
||||
token.
|
||||
|
||||
Run the non-disclosing verifier while holding attended authority to create one
|
||||
ephemeral SecretID:
|
||||
|
||||
```bash
|
||||
python3 scripts/verify_coding_agent_approle.py
|
||||
```
|
||||
|
||||
The verifier holds the SecretID and token only in memory, calls
|
||||
`sys/capabilities-self` rather than a KV read, and revokes the test token before
|
||||
exit. It prints only policy names, TTL, capabilities, and pass/fail state.
|
||||
|
||||
## KeyCape contract
|
||||
|
||||
The accepted service token must contain exactly the following identity
|
||||
coordinates:
|
||||
|
||||
| Claim | Required value |
|
||||
| --- | --- |
|
||||
| `aud` / client id | `codex-railiance-platform` |
|
||||
| `sub` | `service:codex:railiance-platform` |
|
||||
| `principal_type` | `service` |
|
||||
| `tenant` | `tenant:coulomb` |
|
||||
| `roles` | contains `coding-agent` |
|
||||
| requested scope | `openbao:login` |
|
||||
|
||||
The KeyCape access token lifetime is 15 minutes. Its confidential client secret
|
||||
must be generated and delivered through an approved out-of-repository custody
|
||||
path. It must never appear in Git, State Hub, command arguments, logs, or chat.
|
||||
|
||||
## KeyCape-backed OpenBao role
|
||||
|
||||
The migration-target role body is
|
||||
`openbao/auth/coding-agent-jwt-role.json`. Apply it only with attended platform
|
||||
authority:
|
||||
|
||||
```bash
|
||||
bao write auth/netkingdom/role/coding-agent \
|
||||
@openbao/auth/coding-agent-jwt-role.json
|
||||
```
|
||||
|
||||
The role binds the exact service audience, subject, principal type, tenant, and
|
||||
role. It produces a 15-minute, eight-use token without the default policy. The
|
||||
token deliberately combines `agent-high-risk-boundary` with
|
||||
`workload-kv-read-issue-core-runtime`: the latter would otherwise read the
|
||||
issue-core bundle, while the former must win with `deny`.
|
||||
|
||||
## Value-safe verification
|
||||
|
||||
Do not verify by attempting `bao kv get`; an unexpected policy error could
|
||||
print a protected value. After KeyCape exchanges the client credential and the
|
||||
JWT login produces an OpenBao token through a mode-0600 temporary file, use only
|
||||
capabilities and token metadata:
|
||||
|
||||
```bash
|
||||
BAO_TOKEN_FILE=/run/user/$(id -u)/coding-agent.openbao-token
|
||||
BAO_TOKEN=$(head -n 1 "$BAO_TOKEN_FILE") \
|
||||
bao token capabilities \
|
||||
platform/data/workloads/issue-core/issue-core/issue-core-runtime
|
||||
BAO_TOKEN=$(head -n 1 "$BAO_TOKEN_FILE") \
|
||||
bao token capabilities \
|
||||
platform/metadata/workloads/issue-core/issue-core/issue-core-runtime
|
||||
BAO_TOKEN=$(head -n 1 "$BAO_TOKEN_FILE") bao token lookup -format=json \
|
||||
| jq '{display_name:.data.display_name,policies:.data.policies,ttl:.data.ttl}'
|
||||
```
|
||||
|
||||
Expected results are `deny` for the data path and `read` for the metadata path.
|
||||
The lookup must list both policies, omit `default`, and report a TTL no greater
|
||||
than 15 minutes. Shred the temporary JWT and OpenBao token files after the
|
||||
check. Revoke the issued OpenBao token if it remains live.
|
||||
|
||||
## Abort and rollback
|
||||
|
||||
- Abort if any bound claim differs, the JWT issuer/audience is not the live
|
||||
KeyCape contract, or a token would be printed.
|
||||
- Delete `auth/netkingdom/role/coding-agent` if deny-wins does not appear in
|
||||
capabilities or the token includes an unexpected policy.
|
||||
- Delete `auth/approle/role/coding-agent-railiance-platform` if its metadata
|
||||
differs from the reviewed body or its verifier fails.
|
||||
- Revoke the test OpenBao token, disable the KeyCape client, and rotate its
|
||||
confidential secret after suspected disclosure.
|
||||
|
|
@ -22,6 +22,23 @@ modified.
|
|||
catalog entries share the Binky IMAP path and Core Hub is an additional
|
||||
reviewed deny without a catalog lane.
|
||||
|
||||
### 2026-08-22 generated-input reconciliation
|
||||
|
||||
- Ops-warden delivered generated artifact revision
|
||||
`55f0f47a021375b8b25c924953d1b49a24e002c5`: 19 high-risk lanes, 14
|
||||
concrete entries (13 unique paths), and five pattern/non-KV lanes.
|
||||
- The corrected grades add issue-core runtime (whose bundle also contains
|
||||
`GITEA_BACKEND_TOKEN`) and reuse-surface runtime (whose bundle also contains
|
||||
`REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET`). Both data paths now have explicit
|
||||
deny blocks and read-only metadata blocks.
|
||||
- `scripts/agent_high_risk_boundary.py` verified that the vendored input equals
|
||||
the clean upstream artifact aside from generation time and that every entry
|
||||
is covered. Forty-nine focused tests passed.
|
||||
- Under attended `platform-admin` OIDC, the policy was uploaded and read back.
|
||||
OpenBao added only its normal trailing newline. The server-backed ops-warden
|
||||
audit reported 19 high-risk lanes, 14 covered, zero uncovered, and five with
|
||||
no concrete address.
|
||||
|
||||
## Attachment audit and residual blocker
|
||||
|
||||
A metadata-only scan listed and read role configuration under netkingdom OIDC,
|
||||
|
|
@ -30,14 +47,33 @@ Kubernetes auth, AppRole, and token roles. It found:
|
|||
- roles attaching `agent-high-risk-boundary`: **0**;
|
||||
- roles combining it with any `workload-kv-read-*` policy: **0**.
|
||||
|
||||
The live policy is therefore complete but is not automatically attached to a
|
||||
coding-agent identity. The documented manual short-lived token example is not
|
||||
a standing identity and carries no workload-read policy. Attaching the boundary
|
||||
to `platform-admin` would incorrectly constrain the attended operator role and
|
||||
erase the human/agent distinction, so that change was not made.
|
||||
The 2026-08-21 scan accurately found no attachment at that time. On 2026-08-22,
|
||||
State Hub decision `f0955252-7b20-4c80-86e9-f8080ec60793` selected the
|
||||
platform-owned `coding-agent-railiance-platform` AppRole as the operational
|
||||
machine identity while retaining the KeyCape JWT role as an issuer-backed
|
||||
migration target.
|
||||
|
||||
The remaining work is an identity-owner decision: define a distinct coding-
|
||||
agent issuance path, attach the boundary, and prove that deny wins when a
|
||||
workload read policy is also present. A versioned generated list of concrete
|
||||
high-risk deny paths is also requested from ops-warden so policy coverage does
|
||||
not depend on manual catalog transcription.
|
||||
Live AppRole metadata matched the reviewed source:
|
||||
|
||||
- `bind_secret_id=true`, `secret_id_ttl=300`, and
|
||||
`secret_id_num_uses=1`;
|
||||
- `token_ttl=900`, `token_max_ttl=900`, `token_num_uses=8`, and no default
|
||||
policy;
|
||||
- policies exactly `agent-high-risk-boundary` and
|
||||
`workload-kv-read-issue-core-runtime`.
|
||||
|
||||
The non-disclosing verifier minted one ephemeral SecretID, logged in, and used
|
||||
only token metadata plus `sys/capabilities-self`. The data path resolved to
|
||||
`deny`; its metadata path resolved to `read`; the token TTL was 900 seconds.
|
||||
The first verification exposed that a no-default-policy token could not revoke
|
||||
itself. That bounded test token was located and revoked by accessor under
|
||||
attended authority, `auth/token/revoke-self:update` was added to the boundary,
|
||||
and the repeat verification passed and self-revoked without warning. No KV read
|
||||
was attempted and no SecretID, token, or protected value was printed or logged.
|
||||
|
||||
The exact-bound JWT role `auth/netkingdom/role/coding-agent` was also applied
|
||||
with audience `codex-railiance-platform`, subject
|
||||
`service:codex:railiance-platform`, `principal_type=service`,
|
||||
`tenant=tenant:coulomb`, and role `coding-agent`. KeyCape client registration
|
||||
is a future identity-hardening handoff; it is not required for the live AppRole
|
||||
attachment proven here.
|
||||
|
|
|
|||
13
openbao/auth/coding-agent-approle.json
Normal file
13
openbao/auth/coding-agent-approle.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"bind_secret_id": true,
|
||||
"secret_id_ttl": "5m",
|
||||
"secret_id_num_uses": 1,
|
||||
"token_policies": [
|
||||
"agent-high-risk-boundary",
|
||||
"workload-kv-read-issue-core-runtime"
|
||||
],
|
||||
"token_ttl": "15m",
|
||||
"token_max_ttl": "15m",
|
||||
"token_num_uses": 8,
|
||||
"token_no_default_policy": true
|
||||
}
|
||||
23
openbao/auth/coding-agent-jwt-role.json
Normal file
23
openbao/auth/coding-agent-jwt-role.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"role_type": "jwt",
|
||||
"bound_audiences": [
|
||||
"codex-railiance-platform"
|
||||
],
|
||||
"user_claim": "sub",
|
||||
"bound_claims_type": "string",
|
||||
"bound_claims": {
|
||||
"sub": "service:codex:railiance-platform",
|
||||
"principal_type": "service",
|
||||
"tenant": "tenant:coulomb",
|
||||
"roles": "coding-agent"
|
||||
},
|
||||
"token_policies": [
|
||||
"agent-high-risk-boundary",
|
||||
"workload-kv-read-issue-core-runtime"
|
||||
],
|
||||
"token_ttl": "15m",
|
||||
"token_max_ttl": "15m",
|
||||
"token_num_uses": 8,
|
||||
"token_no_default_policy": true,
|
||||
"verbose_oidc_logging": false
|
||||
}
|
||||
|
|
@ -22,6 +22,18 @@ path "platform/data/workloads/activity-core/llm-connect/llm-connect-provider-sec
|
|||
path "platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "platform/data/workloads/issue-core/issue-core/issue-core-runtime" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
path "platform/metadata/workloads/issue-core/issue-core/issue-core-runtime" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "platform/data/workloads/reuse/reuse-surface/runtime-secrets" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
path "platform/metadata/workloads/reuse/reuse-surface/runtime-secrets" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "platform/data/workloads/core-hub/runtime" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
|
|
@ -85,3 +97,6 @@ path "sys/capabilities-self" {
|
|||
path "auth/token/lookup-self" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "auth/token/revoke-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
|
|
|||
90
openbao/policies/inputs/ops-warden-high-risk-data-paths.yaml
Normal file
90
openbao/policies/inputs/ops-warden-high-risk-data-paths.yaml
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# GENERATED by ops-warden/scripts/emit_high_risk_paths.py -- do not edit by hand.
|
||||
# Vendored policy input. ops-warden owns the risk grade; railiance-platform owns
|
||||
# whether and how agent-high-risk-boundary denies each path.
|
||||
|
||||
generated_at: "2026-08-21T11:28:33Z"
|
||||
source: ops-warden/registry/routing/catalog.yaml
|
||||
catalog_revision: "55f0f47a021375b8b25c924953d1b49a24e002c5"
|
||||
catalog_revision_date: "2026-08-21T13:26:10+02:00"
|
||||
catalog_dirty: false
|
||||
high_risk_lane_count: 19
|
||||
concrete_path_count: 14
|
||||
|
||||
no_concrete_path:
|
||||
- database-dynamic-credentials
|
||||
- inter-hub-bootstrap-ssh
|
||||
- object-storage-sts
|
||||
- openbao-api-key
|
||||
- ops-warden-warden-sign-token
|
||||
|
||||
paths:
|
||||
- id: agent-harness-binky-mail-approle
|
||||
data_path: tenants/data/binky/company-email/imap
|
||||
metadata_path: tenants/metadata/binky/company-email/imap
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: agent-harness-forgejo-deploy
|
||||
data_path: platform/data/workloads/agent-harness/forgejo-deploy-key
|
||||
metadata_path: platform/metadata/workloads/agent-harness/forgejo-deploy-key
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: audit-core-senders
|
||||
data_path: platform/data/workloads/audit-core/senders
|
||||
metadata_path: platform/metadata/workloads/audit-core/senders
|
||||
owner_repo: ops-mason
|
||||
fields: null
|
||||
- id: binky-company-email-imap
|
||||
data_path: tenants/data/binky/company-email/imap
|
||||
metadata_path: tenants/metadata/binky/company-email/imap
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: binky-qonto-api
|
||||
data_path: tenants/data/binky/qonto-api
|
||||
metadata_path: tenants/metadata/binky/qonto-api
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: email-connect-transactional
|
||||
data_path: platform/data/workloads/email-connect/transactional
|
||||
metadata_path: platform/metadata/workloads/email-connect/transactional
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: forgejo-admin-api-token
|
||||
data_path: platform/data/workloads/forgejo/forgejo-admin
|
||||
metadata_path: platform/metadata/workloads/forgejo/forgejo-admin
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: issue-core-ingestion-api-key
|
||||
data_path: platform/data/workloads/issue-core/issue-core/issue-core-runtime
|
||||
metadata_path: platform/metadata/workloads/issue-core/issue-core/issue-core-runtime
|
||||
owner_repo: railiance-platform
|
||||
fields: [ISSUE_CORE_API_KEY, GITEA_BACKEND_TOKEN]
|
||||
- id: openrouter-llm-connect
|
||||
data_path: platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
||||
metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: railiance-backup-offsite-lane
|
||||
data_path: platform/data/workloads/railiance/backup/offsite-lane
|
||||
metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: rapp-qonto-keycape-client
|
||||
data_path: platform/data/workloads/rapp-qonto/keycape-client
|
||||
metadata_path: platform/metadata/workloads/rapp-qonto/keycape-client
|
||||
owner_repo: key-cape
|
||||
fields: null
|
||||
- id: reuse-surface-hub-write-token
|
||||
data_path: platform/data/workloads/reuse/reuse-surface/runtime-secrets
|
||||
metadata_path: platform/metadata/workloads/reuse/reuse-surface/runtime-secrets
|
||||
owner_repo: railiance-platform
|
||||
fields: [REUSE_SURFACE_TOKEN, REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET]
|
||||
- id: scaleway-bootstrap
|
||||
data_path: platform/data/workloads/railiance/scaleway/bootstrap
|
||||
metadata_path: platform/metadata/workloads/railiance/scaleway/bootstrap
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
- id: whynot-design-npm-publish
|
||||
data_path: platform/data/workloads/coulomb/whynot-design/npm-publish
|
||||
metadata_path: platform/metadata/workloads/coulomb/whynot-design/npm-publish
|
||||
owner_repo: railiance-platform
|
||||
fields: null
|
||||
146
scripts/agent_high_risk_boundary.py
Normal file
146
scripts/agent_high_risk_boundary.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check the platform-owned agent deny policy against ops-warden's risk input."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
REPO_DIR = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_ARTIFACT = (
|
||||
REPO_DIR / "openbao/policies/inputs/ops-warden-high-risk-data-paths.yaml"
|
||||
)
|
||||
DEFAULT_POLICY = REPO_DIR / "openbao/policies/agent-high-risk-boundary.hcl"
|
||||
|
||||
|
||||
def load_artifact(path: Path) -> dict[str, Any]:
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"artifact must be a mapping: {path}")
|
||||
return data
|
||||
|
||||
|
||||
def parse_policy(text: str) -> dict[str, set[str]]:
|
||||
paths: dict[str, set[str]] = {}
|
||||
for match in re.finditer(
|
||||
r'path\s+"([^"]+)"\s*\{[^}]*?capabilities\s*=\s*\[([^\]]*)\]',
|
||||
text,
|
||||
re.DOTALL,
|
||||
):
|
||||
paths[match.group(1)] = {
|
||||
value.strip().strip('"\'')
|
||||
for value in match.group(2).split(",")
|
||||
if value.strip()
|
||||
}
|
||||
return paths
|
||||
|
||||
|
||||
def check_boundary(
|
||||
artifact: dict[str, Any], policy: dict[str, set[str]]
|
||||
) -> dict[str, Any]:
|
||||
errors: list[str] = []
|
||||
rows = artifact.get("paths")
|
||||
no_concrete = artifact.get("no_concrete_path")
|
||||
if not isinstance(rows, list):
|
||||
rows = []
|
||||
errors.append("artifact paths must be a list")
|
||||
if not isinstance(no_concrete, list):
|
||||
no_concrete = []
|
||||
errors.append("artifact no_concrete_path must be a list")
|
||||
if artifact.get("catalog_dirty") is not False:
|
||||
errors.append("artifact catalog_dirty must be false")
|
||||
if artifact.get("concrete_path_count") != len(rows):
|
||||
errors.append("artifact concrete_path_count does not match paths")
|
||||
if artifact.get("high_risk_lane_count") != len(rows) + len(no_concrete):
|
||||
errors.append("artifact high_risk_lane_count does not match its entries")
|
||||
|
||||
seen_ids: set[str] = set()
|
||||
unique_paths: set[str] = set()
|
||||
for row in rows:
|
||||
if not isinstance(row, dict):
|
||||
errors.append("artifact path entry must be a mapping")
|
||||
continue
|
||||
lane_id = row.get("id")
|
||||
data_path = row.get("data_path")
|
||||
metadata_path = row.get("metadata_path")
|
||||
if not isinstance(lane_id, str) or not lane_id:
|
||||
errors.append("artifact path entry has no id")
|
||||
continue
|
||||
if lane_id in seen_ids:
|
||||
errors.append(f"duplicate lane id: {lane_id}")
|
||||
seen_ids.add(lane_id)
|
||||
if not isinstance(data_path, str) or "/data/" not in data_path:
|
||||
errors.append(f"{lane_id}: invalid data_path")
|
||||
continue
|
||||
expected_metadata = data_path.replace("/data/", "/metadata/", 1)
|
||||
if metadata_path != expected_metadata:
|
||||
errors.append(f"{lane_id}: metadata_path does not match data_path")
|
||||
continue
|
||||
unique_paths.add(data_path)
|
||||
if "deny" not in policy.get(data_path, set()):
|
||||
errors.append(f"{lane_id}: data path is not denied: {data_path}")
|
||||
if "read" not in policy.get(metadata_path, set()):
|
||||
errors.append(f"{lane_id}: metadata path is not readable: {metadata_path}")
|
||||
|
||||
return {
|
||||
"catalog_revision": artifact.get("catalog_revision"),
|
||||
"high_risk_lanes": artifact.get("high_risk_lane_count"),
|
||||
"concrete_entries": len(rows),
|
||||
"unique_concrete_paths": len(unique_paths),
|
||||
"no_concrete_paths": len(no_concrete),
|
||||
"errors": errors,
|
||||
"ok": not errors,
|
||||
}
|
||||
|
||||
|
||||
def comparable_artifact(data: dict[str, Any]) -> dict[str, Any]:
|
||||
return {key: value for key, value in data.items() if key != "generated_at"}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--artifact", type=Path, default=DEFAULT_ARTIFACT)
|
||||
parser.add_argument("--policy", type=Path, default=DEFAULT_POLICY)
|
||||
parser.add_argument(
|
||||
"--upstream",
|
||||
type=Path,
|
||||
help="optionally require the vendored artifact to match this upstream copy",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
artifact = load_artifact(args.artifact)
|
||||
policy = parse_policy(args.policy.read_text(encoding="utf-8"))
|
||||
report = check_boundary(artifact, policy)
|
||||
if args.upstream:
|
||||
upstream = load_artifact(args.upstream)
|
||||
if comparable_artifact(artifact) != comparable_artifact(upstream):
|
||||
report["errors"].append("vendored artifact differs from upstream")
|
||||
report["ok"] = False
|
||||
except (OSError, ValueError, yaml.YAMLError) as exc:
|
||||
report = {"ok": False, "errors": [str(exc)]}
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(report, indent=2, sort_keys=True))
|
||||
elif report["ok"]:
|
||||
print(
|
||||
"PASS: agent boundary covers "
|
||||
f"{report['concrete_entries']} high-risk catalog entries at "
|
||||
f"{report['catalog_revision']}"
|
||||
)
|
||||
else:
|
||||
for error in report["errors"]:
|
||||
print(f"FAIL: {error}", file=sys.stderr)
|
||||
return 0 if report["ok"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
140
scripts/verify_coding_agent_approle.py
Normal file
140
scripts/verify_coding_agent_approle.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Mint, verify, and revoke a coding-agent AppRole token without printing it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_ADDR = "https://bao.coulomb.social"
|
||||
DATA_PATH = "platform/data/workloads/issue-core/issue-core/issue-core-runtime"
|
||||
METADATA_PATH = (
|
||||
"platform/metadata/workloads/issue-core/issue-core/issue-core-runtime"
|
||||
)
|
||||
EXPECTED_POLICIES = {
|
||||
"agent-high-risk-boundary",
|
||||
"workload-kv-read-issue-core-runtime",
|
||||
}
|
||||
|
||||
|
||||
def bao_json(args: list[str]) -> dict[str, Any]:
|
||||
completed = subprocess.run(
|
||||
["bao", *args],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
return json.loads(completed.stdout)
|
||||
|
||||
|
||||
def api_request(
|
||||
addr: str,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
token: str | None = None,
|
||||
payload: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if token:
|
||||
headers["X-Vault-Token"] = token
|
||||
body = json.dumps(payload).encode() if payload is not None else None
|
||||
request = urllib.request.Request(
|
||||
f"{addr.rstrip('/')}/v1/{path}", data=body, headers=headers, method=method
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
content = response.read()
|
||||
return json.loads(content) if content else {}
|
||||
|
||||
|
||||
def verify(role: str, addr: str) -> dict[str, Any]:
|
||||
role_payload = bao_json(
|
||||
["read", "-format=json", f"auth/approle/role/{role}/role-id"]
|
||||
)
|
||||
role_id = role_payload["data"]["role_id"]
|
||||
secret_payload = bao_json(
|
||||
["write", "-format=json", "-f", f"auth/approle/role/{role}/secret-id"]
|
||||
)
|
||||
secret_id = secret_payload["data"]["secret_id"]
|
||||
|
||||
agent_token: str | None = None
|
||||
report: dict[str, Any] = {}
|
||||
try:
|
||||
login = api_request(
|
||||
addr,
|
||||
"POST",
|
||||
"auth/approle/login",
|
||||
payload={"role_id": role_id, "secret_id": secret_id},
|
||||
)
|
||||
agent_token = login["auth"]["client_token"]
|
||||
lookup = api_request(addr, "GET", "auth/token/lookup-self", token=agent_token)
|
||||
capabilities = api_request(
|
||||
addr,
|
||||
"POST",
|
||||
"sys/capabilities-self",
|
||||
token=agent_token,
|
||||
payload={"paths": [DATA_PATH, METADATA_PATH]},
|
||||
)
|
||||
policies = set(lookup["data"].get("policies", []))
|
||||
ttl = int(lookup["data"].get("ttl", 0))
|
||||
data_caps = capabilities.get(DATA_PATH, [])
|
||||
metadata_caps = capabilities.get(METADATA_PATH, [])
|
||||
errors: list[str] = []
|
||||
if policies != EXPECTED_POLICIES:
|
||||
errors.append(f"unexpected policies: {sorted(policies)}")
|
||||
if ttl <= 0 or ttl > 900:
|
||||
errors.append(f"TTL outside 1..900 seconds: {ttl}")
|
||||
if data_caps != ["deny"]:
|
||||
errors.append(f"data capabilities are not deny: {data_caps}")
|
||||
if "read" not in metadata_caps or "deny" in metadata_caps:
|
||||
errors.append(f"metadata capabilities are not read-only: {metadata_caps}")
|
||||
report = {
|
||||
"role": role,
|
||||
"policies": sorted(policies),
|
||||
"ttl": ttl,
|
||||
"data_capabilities": data_caps,
|
||||
"metadata_capabilities": metadata_caps,
|
||||
"deny_wins": data_caps == ["deny"],
|
||||
"errors": errors,
|
||||
"ok": not errors,
|
||||
}
|
||||
finally:
|
||||
if agent_token:
|
||||
try:
|
||||
api_request(addr, "POST", "auth/token/revoke-self", token=agent_token)
|
||||
except (OSError, urllib.error.HTTPError):
|
||||
report.setdefault("errors", []).append("test token self-revocation failed")
|
||||
report["ok"] = False
|
||||
return report
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--role", default="coding-agent-railiance-platform")
|
||||
parser.add_argument("--addr", default=os.environ.get("BAO_ADDR", DEFAULT_ADDR))
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
report = verify(args.role, args.addr)
|
||||
except (
|
||||
KeyError,
|
||||
json.JSONDecodeError,
|
||||
OSError,
|
||||
subprocess.CalledProcessError,
|
||||
subprocess.TimeoutExpired,
|
||||
urllib.error.HTTPError,
|
||||
) as exc:
|
||||
report = {"role": args.role, "ok": False, "errors": [str(exc)]}
|
||||
print(json.dumps(report, indent=2, sort_keys=True))
|
||||
return 0 if report["ok"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
|
@ -17,6 +18,14 @@ assert SPEC.loader is not None
|
|||
sys.modules[SPEC.name] = credential_change
|
||||
SPEC.loader.exec_module(credential_change)
|
||||
|
||||
BOUNDARY_SPEC = importlib.util.spec_from_file_location(
|
||||
"agent_high_risk_boundary", REPO_DIR / "scripts/agent_high_risk_boundary.py"
|
||||
)
|
||||
agent_high_risk_boundary = importlib.util.module_from_spec(BOUNDARY_SPEC)
|
||||
assert BOUNDARY_SPEC.loader is not None
|
||||
sys.modules[BOUNDARY_SPEC.name] = agent_high_risk_boundary
|
||||
BOUNDARY_SPEC.loader.exec_module(agent_high_risk_boundary)
|
||||
|
||||
|
||||
class CredentialChangeTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
|
|
@ -155,29 +164,69 @@ class CredentialChangeTests(unittest.TestCase):
|
|||
self.assertEqual(store["spec"]["conditions"][0]["namespaces"], ["core-hub"])
|
||||
|
||||
def test_agent_boundary_denies_every_current_concrete_high_risk_catalog_path(self) -> None:
|
||||
boundary = (
|
||||
REPO_DIR / "openbao/policies/agent-high-risk-boundary.hcl"
|
||||
).read_text()
|
||||
data_paths = {
|
||||
"platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets",
|
||||
"platform/data/workloads/railiance/backup/offsite-lane",
|
||||
"platform/data/workloads/forgejo/forgejo-admin",
|
||||
"tenants/data/binky/company-email/imap",
|
||||
"tenants/data/binky/qonto-api",
|
||||
"platform/data/workloads/coulomb/whynot-design/npm-publish",
|
||||
"platform/data/workloads/rapp-qonto/keycape-client",
|
||||
"platform/data/workloads/agent-harness/forgejo-deploy-key",
|
||||
"platform/data/workloads/audit-core/senders",
|
||||
"platform/data/workloads/email-connect/transactional",
|
||||
"platform/data/workloads/railiance/scaleway/bootstrap",
|
||||
}
|
||||
for path in data_paths:
|
||||
with self.subTest(path=path):
|
||||
self.assertRegex(
|
||||
boundary,
|
||||
rf'path "{path}" \{{\s*capabilities = \["deny"\]',
|
||||
)
|
||||
artifact = agent_high_risk_boundary.load_artifact(
|
||||
REPO_DIR
|
||||
/ "openbao/policies/inputs/ops-warden-high-risk-data-paths.yaml"
|
||||
)
|
||||
boundary = agent_high_risk_boundary.parse_policy(
|
||||
(REPO_DIR / "openbao/policies/agent-high-risk-boundary.hcl").read_text()
|
||||
)
|
||||
report = agent_high_risk_boundary.check_boundary(artifact, boundary)
|
||||
self.assertEqual(report["errors"], [])
|
||||
self.assertEqual(report["high_risk_lanes"], 19)
|
||||
self.assertEqual(report["concrete_entries"], 14)
|
||||
self.assertEqual(report["unique_concrete_paths"], 13)
|
||||
self.assertEqual(report["no_concrete_paths"], 5)
|
||||
self.assertEqual(boundary["auth/token/revoke-self"], {"update"})
|
||||
|
||||
def test_coding_agent_jwt_role_is_exact_bound_and_deny_wins(self) -> None:
|
||||
role = json.loads(
|
||||
(REPO_DIR / "openbao/auth/coding-agent-jwt-role.json").read_text()
|
||||
)
|
||||
self.assertEqual(role["role_type"], "jwt")
|
||||
self.assertEqual(role["bound_audiences"], ["codex-railiance-platform"])
|
||||
self.assertEqual(role["user_claim"], "sub")
|
||||
self.assertEqual(
|
||||
role["bound_claims"],
|
||||
{
|
||||
"sub": "service:codex:railiance-platform",
|
||||
"principal_type": "service",
|
||||
"tenant": "tenant:coulomb",
|
||||
"roles": "coding-agent",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
set(role["token_policies"]),
|
||||
{
|
||||
"agent-high-risk-boundary",
|
||||
"workload-kv-read-issue-core-runtime",
|
||||
},
|
||||
)
|
||||
self.assertTrue(role["token_no_default_policy"])
|
||||
self.assertEqual(role["token_ttl"], "15m")
|
||||
self.assertEqual(role["token_max_ttl"], "15m")
|
||||
self.assertEqual(role["token_num_uses"], 8)
|
||||
|
||||
def test_coding_agent_approle_is_bounded_and_deny_wins(self) -> None:
|
||||
role = json.loads(
|
||||
(REPO_DIR / "openbao/auth/coding-agent-approle.json").read_text()
|
||||
)
|
||||
self.assertTrue(role["bind_secret_id"])
|
||||
self.assertEqual(role["secret_id_ttl"], "5m")
|
||||
self.assertEqual(role["secret_id_num_uses"], 1)
|
||||
self.assertEqual(
|
||||
set(role["token_policies"]),
|
||||
{
|
||||
"agent-high-risk-boundary",
|
||||
"workload-kv-read-issue-core-runtime",
|
||||
},
|
||||
)
|
||||
self.assertTrue(role["token_no_default_policy"])
|
||||
self.assertEqual(role["token_ttl"], "15m")
|
||||
self.assertEqual(role["token_max_ttl"], "15m")
|
||||
self.assertEqual(role["token_num_uses"], 8)
|
||||
|
||||
def test_core_hub_database_lanes_are_split_and_exact_scope(self) -> None:
|
||||
database_policy = (
|
||||
REPO_DIR / "openbao/policies/external-secrets-core-hub-database.hcl"
|
||||
).read_text()
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ type: workplan
|
|||
title: "Close agent high-risk OpenBao boundary coverage"
|
||||
domain: financials
|
||||
repo: railiance-platform
|
||||
status: blocked
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: railiance
|
||||
created: "2026-08-21"
|
||||
updated: "2026-08-21"
|
||||
updated: "2026-08-22"
|
||||
related:
|
||||
- WARDEN-WP-0032
|
||||
- RISK-F-0009
|
||||
|
|
@ -43,9 +43,11 @@ priority: high
|
|||
state_hub_task_id: "44d1a4bf-70bb-4d50-a40a-2158acc96b36"
|
||||
```
|
||||
|
||||
Run the capabilities-only ops-warden audit against the policy. The 2026-08-21
|
||||
reconciliation found 17 high-risk lanes: six covered, six concrete uncovered,
|
||||
and five without a concrete KV address. No credential value was read.
|
||||
Run the capabilities-only ops-warden audit against the policy. The initial
|
||||
2026-08-21 reconciliation found 17 high-risk lanes: six covered, six concrete
|
||||
uncovered, and five without a concrete KV address. The final generated input
|
||||
contains 19 high-risk lanes, 14 concrete entries, and five without an address.
|
||||
No credential value was read.
|
||||
|
||||
## T02 — Close the concrete deny gaps
|
||||
|
||||
|
|
@ -61,9 +63,10 @@ rapp-qonto Keycape client, agent-harness Forgejo deploy key, audit-core senders,
|
|||
email-connect transactional, and Scaleway bootstrap. Add regression coverage
|
||||
for every concrete path currently emitted by the catalog audit.
|
||||
|
||||
Completed 2026-08-21. The source policy covers all 12 unique concrete paths
|
||||
(including the Core Hub path, which has no catalog lane), and the local
|
||||
catalog audit reports all 12 catalog entries covered with none uncovered.
|
||||
Completed 2026-08-22. After ops-warden corrected two multi-field grades, the
|
||||
source policy covers all 13 unique concrete catalog paths plus the additional
|
||||
reviewed Core Hub path. The generated-input check reports all 14 concrete
|
||||
catalog entries covered with none uncovered.
|
||||
|
||||
## T03 — Apply and verify live
|
||||
|
||||
|
|
@ -79,13 +82,11 @@ and rerun the catalog audit with `policy_source: server`. Inspect policy/token
|
|||
role metadata to determine whether an agent identity carries the boundary and
|
||||
whether any role combines it with a workload-read policy. Do not mint a token.
|
||||
|
||||
Completed 2026-08-21 under attended `platform-admin` OIDC. OpenBao accepted the
|
||||
policy; normalized readback matched source, and the server-backed catalog audit
|
||||
reported 17 high-risk lanes, 12 covered entries, zero uncovered, and five
|
||||
pattern/non-KV lanes without a concrete address. Metadata-only inspection of
|
||||
all discoverable netkingdom, Kubernetes, AppRole, and token roles found zero
|
||||
attachments of `agent-high-risk-boundary` and therefore zero roles combining
|
||||
it with a workload-read policy. No token was minted and no Secret was read.
|
||||
Completed initially on 2026-08-21 and reconciled again on 2026-08-22 under
|
||||
attended `platform-admin` OIDC. OpenBao accepted the policy; normalized
|
||||
readback matched source, and the final server-backed audit reported 19
|
||||
high-risk lanes, 14 covered entries, zero uncovered, and five pattern/non-KV
|
||||
lanes without a concrete address. No Secret value was read.
|
||||
|
||||
## T04 — Route the result
|
||||
|
||||
|
|
@ -105,11 +106,15 @@ Completed 2026-08-21 via State Hub message
|
|||
attachment finding, the distinct-agent-identity blocker, and the requested
|
||||
versioned generated artifact shape.
|
||||
|
||||
Ops-warden delivered revision `55f0f47a021375b8b25c924953d1b49a24e002c5`
|
||||
on 2026-08-21. The platform now vendors it as a checked policy input and has a
|
||||
repeatable upstream-equality plus policy-coverage command.
|
||||
|
||||
## T05 — Establish a distinct coding-agent identity
|
||||
|
||||
```task
|
||||
id: RAILIANCE-WP-0022-T05
|
||||
status: wait
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "47aa5ed9-95c5-4a23-a460-e4bbd3ed6f65"
|
||||
```
|
||||
|
|
@ -119,12 +124,40 @@ The policy is live but no role attaches it. Do not add the boundary to
|
|||
recovery work may require the protected values. The identity owner must define
|
||||
a distinct coding-agent issuance path, attach this boundary there, and prove
|
||||
deny-wins behavior when combined with an otherwise readable workload policy.
|
||||
This is blocked on an identity-owner decision and is not invented here.
|
||||
Completed 2026-08-22. State Hub decision
|
||||
`f0955252-7b20-4c80-86e9-f8080ec60793` establishes the platform-owned AppRole
|
||||
`coding-agent-railiance-platform` as the operational machine identity. Its
|
||||
single-use SecretIDs expire after five minutes; its tokens last at most 15
|
||||
minutes, have eight uses, omit the default policy, and attach both
|
||||
`agent-high-risk-boundary` and the otherwise-readable issue-core workload
|
||||
policy. A live ephemeral login proved data capabilities resolve to `deny` and
|
||||
metadata to `read`; the token was revoked without reading the KV value.
|
||||
|
||||
The exact-bound KeyCape JWT role is also live as the issuer-backed migration
|
||||
target. KeyCape principal registration remains an identity hardening follow-up,
|
||||
not a blocker to the active AppRole boundary.
|
||||
|
||||
## T06 — Consume the generated catalog artifact
|
||||
|
||||
```task
|
||||
id: RAILIANCE-WP-0022-T06
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Consume ops-warden's versioned `high-risk-data-paths.yaml` as a policy-check
|
||||
input, add the two newly regraded issue-core and reuse-surface paths, and rerun
|
||||
source plus live coverage. The input remains ops-warden's risk statement, not
|
||||
the policy authority; railiance-platform continues to own every deny decision.
|
||||
|
||||
Completed 2026-08-22. The vendored input matches the clean upstream artifact,
|
||||
the repository check covers all 14 entries, and the server-backed audit reports
|
||||
zero uncovered. Evidence: `docs/evidence/agent-high-risk-boundary-2026-08-21.md`.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [x] Every concrete high-risk catalog path is denied in the source policy.
|
||||
- [x] The live policy matches source and the server-backed audit passes.
|
||||
- [x] Agent boundary attachment is established from metadata (currently zero).
|
||||
- [x] A distinct AppRole attachment is established from live metadata.
|
||||
- [x] Result and generated-artifact follow-up are routed to ops-warden.
|
||||
- [ ] A distinct coding-agent identity actually attaches the boundary.
|
||||
- [x] A distinct coding-agent identity actually attaches the boundary.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue