From 6a0daae43729087ae73edf5e40bfeeda8f362f3e Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 6 Sep 2026 00:25:37 +0200 Subject: [PATCH] feat: prepare data-only Anthropic native delivery lane Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb --- catalog/glas-claude-agent-dev-anthropic.yaml | 60 +++++++++++++++++++ docs/catalog-admission.md | 7 +++ docs/glas-claude-delivery.md | 53 ++++++++++++++++ src/secrets_engine/catalog.py | 2 + src/secrets_engine/roles.py | 8 +-- tests/test_guards.py | 17 ++++++ ...ETS-WP-0009-glas-claude-native-delivery.md | 59 ++++++++++++++++++ 7 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 catalog/glas-claude-agent-dev-anthropic.yaml create mode 100644 docs/glas-claude-delivery.md create mode 100644 workplans/SECRETS-WP-0009-glas-claude-native-delivery.md diff --git a/catalog/glas-claude-agent-dev-anthropic.yaml b/catalog/glas-claude-agent-dev-anthropic.yaml new file mode 100644 index 0000000..149009a --- /dev/null +++ b/catalog/glas-claude-agent-dev-anthropic.yaml @@ -0,0 +1,60 @@ +id: glas-claude-agent-dev-anthropic +kind: kv +org: coulomb +repo: sand-boxer +stage: prod +description: Proposed native exec-env delivery for CCR-2026-0016. KV custody exists; + no runtime grant or activation yet. +mount: platform +path: workloads/glas-harness/claude-agent-dev +mount_management: existing +fields: +- ANTHROPIC_API_KEY +consumers: +- name: sand-boxer-glas-agent-dev + auth: approle + claim: catalog:glas-claude-agent-dev-anthropic + purpose: Owner-admitted glas-harness agt run through the reviewed local profile; + no caller-facing key fetch +workload_delivery: [] +delivery_modes: +- exec-env +- read-check +delivery_auth: + method: approle + management: engine + policy_name: se-prod-glas-claude-agent-dev-anthropic + role_name: se-prod-glas-claude-agent-dev-anthropic + metadata_read: false + token_ttl: 5m + token_max_ttl: 15m + secret_id_ttl: 5m + secret_id_num_uses: 1 + token_num_uses: 8 +approval: + model: ccr + decision_ref: CCR-2026-0016 + notes: Custody only has been completed. Native apply and exec require durable exact-action + authorization, engine consume, scoped backend authority and verified delivery + state. This entry is not authorization. +verification: + positive: Exact scoped AppRole reads only ANTHROPIC_API_KEY into the approved child; + owner binding and redaction pass. + negative: Wrong owner profile/project/actor, direct caller fetch, sibling KV, metadata, + listing and writes denied. +risk: + classification: high + notes: API spend; provider expiry 2027-01-31T21:00:00Z is not enforced by Bao token + TTL. Workspace scope and budget unverified. +rotation: + owner: railiance-platform + sand-boxer + expectation: Provider replacement, versioned CAS custody, stop old runs, verify + replacement then revoke predecessor at Anthropic and prove denial. + ttl: provider-defined +deactivation: + owner: railiance-platform + sand-boxer + expectation: Disable lane, stop affected runs, revoke Bao sessions and provider + key. Preserve custody history. +audit: + evidence: CCR id, actor, exact path, field name, provider key identifier if non-secret, + timestamps, and pass/fail only diff --git a/docs/catalog-admission.md b/docs/catalog-admission.md index e91c1d5..7b31c9f 100644 --- a/docs/catalog-admission.md +++ b/docs/catalog-admission.md @@ -120,3 +120,10 @@ Before accepting an existing production lane: that approval cannot be resolved. 8. Preserve the interim route until native positive and negative verification passes without exposing a value. + +### Optional data-only KV read policy + +`delivery_auth.metadata_read` is a boolean, defaulting to `true` for existing +lanes. Set `false` when the consumer needs only the KV data GET; generated +policy omits the metadata endpoint. Owner metadata verification then needs +separate operator authority, not broader consumer access. diff --git a/docs/glas-claude-delivery.md b/docs/glas-claude-delivery.md new file mode 100644 index 0000000..2876967 --- /dev/null +++ b/docs/glas-claude-delivery.md @@ -0,0 +1,53 @@ +# Glas Claude exec delivery + +Proposed native lane `glas-claude-agent-dev-anthropic`, provenance +railiance-platform CCR-2026-0016; implementation/activation record SECRETS-WP-0009. +KV custody is already confirmed at version 2. Do not provision or rotate it as +part of native read-lane adoption. + +The generated plan checks existing mount `platform`, creates policy and AppRole +`se-prod-glas-claude-agent-dev-anthropic`, and grants read only on +`platform/data/workloads/glas-harness/claude-agent-dev`. Field ANTHROPIC_API_KEY +is selected by the exec adapter; KV policies scope entries, not fields. +`delivery_auth.metadata_read: false` excludes the metadata endpoint; existing +lanes retain their previous metadata access by default. Token TTL 5m, maximum +15m, SecretID TTL 5m and single use, token use budget 8. No wildcard, listing, +workload writes, mount mutation, provider creation or default-policy change is +included in this plan. Verify effective token identity policies at activation. + +Sand-boxer's owner-configured credential route binds profile, project, actor and +nonempty run id before invoking secrets-engine's exec-env interface. The +provider injects the key into a private host helper that directly forwards it to +the namespace broker. The broker injects only ANTHROPIC_API_KEY into the command +and redacts exact values before truncating output. No OpenBao token crosses +into the sandbox; no key is returned through Glas's API. Values are available +to the trusted workload and descendants; encoding/exfiltration by hostile +workload code is not prevented by an output redactor. Existing sandbox, egress, +artifact verification and profile admission boundaries remain required. + +A synthetic provider proves the transport only. It does not stand in for native +approval, OpenBao access, provider authentication or production readiness. + +## Activation requirements + +The current engine's production stance refuses before opening the backend: +`production action 'exec' requires a durable access-engine decision record; +live production remains disabled`. This refusal was exercised with the proposed +catalog and service-jwt selection. No real value was requested. + +Activation depends on SECRETS-WP-0007-T04 (exact production actions) and +SECRETS-WP-0008-T02/T06 (decision consumption and service authority). Require +canonical ActionAuthorization for each protected action, successful consume, +and exact scoped backend authority. This draft cannot authorize itself; an +operator browser token or unsafe-demo flag is not a runtime substitute. + +Once those services exist: obtain the reviewed apply authorization, apply this +exact policy/AppRole with scoped authority, verify positive read and denied +metadata/sibling/write access without exposing values, and record delivery-ready +state. Bind approved exec authorization and named engine service authentication +to the sand-boxer owner route. Prove actual provider authentication and a bounded +Glas task, then activate routing and only the validated profile. + +Rotation: store replacement with CAS, stop old runs, verify replacement, revoke +predecessor at Anthropic and prove denial. Bao session expiration does not revoke +the provider key. Compromise disables the provider key and affected runs first. diff --git a/src/secrets_engine/catalog.py b/src/secrets_engine/catalog.py index 736c214..0b063e9 100644 --- a/src/secrets_engine/catalog.py +++ b/src/secrets_engine/catalog.py @@ -284,6 +284,8 @@ def validate_entry(data: dict[str, Any], *, source: str = "") -> Catalog delivery_auth = data["delivery_auth"] if not isinstance(delivery_auth, dict): raise CatalogError(f"{source}: delivery_auth must be a mapping") + if "metadata_read" in delivery_auth and not isinstance(delivery_auth["metadata_read"], bool): + raise CatalogError(f"{source}: delivery_auth.metadata_read must be boolean") auth_method = delivery_auth.get("method", "approle") auth_management = delivery_auth.get("management", "engine") if auth_method not in VALID_DELIVERY_AUTH_METHODS: diff --git a/src/secrets_engine/roles.py b/src/secrets_engine/roles.py index 35d0500..fb93dd4 100644 --- a/src/secrets_engine/roles.py +++ b/src/secrets_engine/roles.py @@ -175,10 +175,10 @@ def lane_policy_paths(entry: CatalogEntry) -> dict[str, list[str]]: """The minimal KV v2 paths + capabilities a consumer policy needs for a lane.""" data_path = f"{entry.mount}/data/{entry.path}" meta_path = f"{entry.mount}/metadata/{entry.path}" - return { - data_path: ["read"], - meta_path: ["read"], - } + paths = {data_path: ["read"]} + if entry.delivery_auth.get("metadata_read", True): + paths[meta_path] = ["read"] + return paths def render_policy_hcl(policy_name: str, paths: dict[str, list[str]]) -> str: diff --git a/tests/test_guards.py b/tests/test_guards.py index 437d221..1a41687 100644 --- a/tests/test_guards.py +++ b/tests/test_guards.py @@ -114,3 +114,20 @@ def test_every_admitted_lane_renders_existing_mount_check_and_exact_policy(): assert f'path "{entry.kv_data_path}"' in plan.policy_hcl assert "*" not in entry.kv_data_path assert plan.role_name.startswith("se-prod-") + + +def test_metadata_read_can_be_excluded_without_changing_default(): + from secrets_engine.roles import lane_policy_paths + default = _entry(stage="test", path="test/team/thing") + assert "secret/metadata/test/team/thing" in lane_policy_paths(default) + narrow = _entry(stage="test", path="test/team/thing", delivery_auth={ + "method":"approle", "management":"engine", "metadata_read":False}) + assert lane_policy_paths(narrow) == {"secret/data/test/team/thing":["read"]} + assert "metadata/" not in build_plan(narrow, "test").policy_hcl + + +@pytest.mark.parametrize("invalid", ["false", None, 0, [], {}]) +def test_metadata_read_setting_requires_boolean(invalid): + from secrets_engine.errors import CatalogError + with pytest.raises(CatalogError, match="metadata_read must be boolean"): + _entry(delivery_auth={"metadata_read":invalid}) diff --git a/workplans/SECRETS-WP-0009-glas-claude-native-delivery.md b/workplans/SECRETS-WP-0009-glas-claude-native-delivery.md new file mode 100644 index 0000000..40e68e1 --- /dev/null +++ b/workplans/SECRETS-WP-0009-glas-claude-native-delivery.md @@ -0,0 +1,59 @@ +--- +id: SECRETS-WP-0009 +type: workplan +title: "Activate native Claude credential delivery for Glas" +domain: infotech +repo: secrets-engine +status: blocked +owner: codex +created: "2026-09-05" +updated: "2026-09-05" +--- + +Demand: GLAS-WP-0012-T02 / SAND-WP-0015-T04, custody CCR-2026-0016. +The user authorized continuing credential delivery after storing the key in Bao. +This record tracks native read-lane adoption, not a new provider key or rotation. + +## Define the exact native read lane + +```task +id: SECRETS-WP-0009-T01 +status: done +priority: high +``` + +Added catalog glas-claude-agent-dev-anthropic; plan checks existing platform +mount and proposes one data-only read policy/AppRole. Token TTL5m/max15m, +single-use SecretID5m, token use budget8. See docs/glas-claude-delivery.md. +No metadata, sibling, listing or write capability is proposed. + +## Support data-only delivery policies + +```task +id: SECRETS-WP-0009-T02 +status: done +priority: high +``` + +Added boolean delivery_auth.metadata_read with compatible default true and +explicit false for this lane. Validation refuses non-booleans; generated plan +omits metadata permissions when disabled. Full owner suite passes. Sand-boxer +synthetic exec-env transport proof passed; no real secret was read. + +## Activate the approved native lane and verify real owner delivery + +```task +id: SECRETS-WP-0009-T03 +status: wait +priority: high +``` + +Depends on SECRETS-WP-0007-T04 and SECRETS-WP-0008-T02/T06: canonical production +authorization, successful consume and scoped service authority must exist. +Current production exec refuses before OpenBao because durable access-engine +decision records are not served. Do not bypass this with unsafe-demo or a human +operator runtime token. Then apply the exact read policy/AppRole, prove positive +and negative access, register delivery-ready evidence, bind owner exec with +service authentication, and return verified pins/evidence to SAND-WP-0015 and +GLAS-WP-0012. Provider scope/budget and expiry remain explicit acceptance inputs. +Keep the catalog route inactive until real verification passes.