Bind native OpenRouter approval to custody and delivery inputs
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
This commit is contained in:
parent
0783b50216
commit
7ba1b6223e
16 changed files with 656 additions and 7 deletions
81
docs/evidence/2026-09-14-openrouter-apply-request.json
Normal file
81
docs/evidence/2026-09-14-openrouter-apply-request.json
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"tenant": "tenant:platform",
|
||||
"subject": {
|
||||
"id": "secrets-engine",
|
||||
"type": "service"
|
||||
},
|
||||
"action": "apply",
|
||||
"resource": {
|
||||
"id": "catalog:openrouter-llm-connect",
|
||||
"type": "secret-catalog-lane",
|
||||
"system": "secrets-engine",
|
||||
"attributes": {
|
||||
"stage": "prod",
|
||||
"fields": [],
|
||||
"policy_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
],
|
||||
"auth_targets": [
|
||||
"se-prod-openrouter-llm-connect"
|
||||
]
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference",
|
||||
"catalog_target": {
|
||||
"kind": "kv",
|
||||
"org": "coulomb",
|
||||
"repo": "llm-connect",
|
||||
"mount": "platform",
|
||||
"path": "workloads/activity-core/llm-connect/llm-connect-provider-secrets",
|
||||
"fields": [
|
||||
"OPENROUTER_API_KEY"
|
||||
],
|
||||
"mount_management": "existing",
|
||||
"consumers": [
|
||||
{
|
||||
"name": "llm-connect-activity-core",
|
||||
"auth": "kubernetes",
|
||||
"claim": "serviceaccount:external-secrets/external-secrets",
|
||||
"purpose": "sync llm-connect-provider-secrets for the activity-core deployment"
|
||||
},
|
||||
{
|
||||
"name": "intelligence-radar-key-check",
|
||||
"auth": "approle",
|
||||
"claim": "catalog:openrouter-llm-connect",
|
||||
"purpose": "IR-WP-0004 read-only OpenRouter key authentication check; no inference"
|
||||
}
|
||||
],
|
||||
"delivery_modes": [
|
||||
"exec-env",
|
||||
"read-check"
|
||||
],
|
||||
"delivery_auth": {
|
||||
"method": "approle",
|
||||
"management": "engine",
|
||||
"policy_name": "se-prod-openrouter-llm-connect",
|
||||
"role_name": "se-prod-openrouter-llm-connect",
|
||||
"token_ttl": "15m",
|
||||
"token_max_ttl": "30m",
|
||||
"secret_id_ttl": "15m",
|
||||
"secret_id_num_uses": 1,
|
||||
"token_num_uses": 8
|
||||
},
|
||||
"delivery_config": {
|
||||
"exec_owner": {
|
||||
"status": "pending",
|
||||
"owner": "intelligence-radar-key-check",
|
||||
"reason": "Pin tools/openrouter_key_check.py and its protected Python runtime, exact argv, private cwd and fixed environment after custody-owner admission. The pending binding refuses exec before consume or secret retrieval."
|
||||
}
|
||||
},
|
||||
"auth_capability": {},
|
||||
"workload_delivery": [
|
||||
{
|
||||
"mode": "external-secrets",
|
||||
"owner": "railiance-platform + llm-connect"
|
||||
}
|
||||
]
|
||||
},
|
||||
"human_control": true
|
||||
}
|
||||
}
|
||||
66
docs/evidence/2026-09-14-openrouter-approval-exercise.json
Normal file
66
docs/evidence/2026-09-14-openrouter-approval-exercise.json
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"target": "disposable local processes; synthetic credentials",
|
||||
"started_at": "2026-09-13T22:42:40.649405+00:00",
|
||||
"keycape_image": "forgejo.coulomb.social/coulomb/key-cape@sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611",
|
||||
"approval_engine_commit": "a0a602976eef818f36dde35f76f7f2e589bd051b",
|
||||
"flex_auth_commit": "25712f2aad9c695ce4ce1060490432d2ffbf80ce",
|
||||
"keycape_contract_commit": "6bcc2a5919799ceef68bd942d4f460ea24c87181",
|
||||
"consumer_source_sha256": {
|
||||
"approval_auth.py": "6f3b033e7928e1c527bc19f5a1e01bf243540769a8646bd8cd47268da25637b5",
|
||||
"approval_consume.py": "4515c74dbe1cea030cdccebf17b27642ad4f67f2aaee4f3a87e4bb2b9cfad295",
|
||||
"approval_claim.py": "20b728d2d3e14ffc89fa21a137716015875f9a05c6af7ff48d07cf48d89503a9",
|
||||
"authorization.py": "3ffcc76404af7d97d79219a5ed6fa6040516477857f530c8d5a5e45e38871def",
|
||||
"config.py": "2f2f1b60664d89bbb806aed0c768378923062077740e1f96e4c359238eb06955",
|
||||
"service_auth.py": "653723ef5babce2157771416d93cb15cdbb7caca3c45509c3730520c55e4515c",
|
||||
"cli.py": "1147d0e60850cfc38ee21e8564f51656d364ddc3cd8d6353a0c2dd6a0b78b8ec",
|
||||
"catalog.py": "247c29314bcf86b3439d756ed911df62fb43c3c498b18ce5403704986a501b18",
|
||||
"exec_owner.py": "10c21e7e37dd3d0aad41c4bb15df8f4a69c22d9046105a981a9613a91cd4c145",
|
||||
"exec_delivery.py": "b142b60d875acf3dea1ba3ae99182a7ed7328d955dee5be2d12c977fc5646e8e",
|
||||
"routing.py": "9d5050d7494a207394ce8251d8aac574ca0c08e3252639b5f05b373d6e35a076"
|
||||
},
|
||||
"limitations": [
|
||||
"standalone Flex Auth source, not deployed pin",
|
||||
"local Approval Engine source, not deployed image",
|
||||
"no live custody or client-side read grant",
|
||||
"no OpenBao effect or model execution",
|
||||
"positive human entry seeded in disposable store; no human JWT/PKCE proof"
|
||||
],
|
||||
"checks": {
|
||||
"operator_issued_and_approved_via_verified_jwt": true,
|
||||
"wrong_action_refused_before_consume": true,
|
||||
"producer_origin_join_with_carried_claim": true,
|
||||
"actual_consumer_claim_check_consume": true,
|
||||
"same_digest_retry_idempotent": true,
|
||||
"different_digest_refused": true,
|
||||
"spent_claim_refused": true,
|
||||
"operator_consume_scope_denied_by_issuer": true,
|
||||
"wrong_secret_refused": true,
|
||||
"no_access_token_file_created": true,
|
||||
"registry_override_accepts_exact_submission": true,
|
||||
"same_enriched_result_different_submission_refused": true,
|
||||
"real_dual_control_denial_then_claim_check_consume": true,
|
||||
"declared_control_refuses_real_keycape_service_bind": true,
|
||||
"undeclared_control_refused_before_real_pdp_consume_backend": true,
|
||||
"catalog_path_change_refused_before_consume_backend": true,
|
||||
"catalog_mount_change_refused_before_consume_backend": true,
|
||||
"catalog_owner_change_refused_before_consume_backend": true,
|
||||
"catalog_limits_change_refused_before_consume_backend": true,
|
||||
"exec_owner_substitution_refused_before_consume_backend": true,
|
||||
"exec_owner_changed_environment_replay_refused_by_real_pdp_join": true,
|
||||
"exec_owner_real_cli_consumes_before_fixture_backend": true,
|
||||
"exec_owner_actual_child_excludes_parent_credentials": true,
|
||||
"exec_owner_digest_preserved_by_real_evaluator": true,
|
||||
"declared_human_intent_preserved_by_real_evaluator": true,
|
||||
"declared_human_fixture_consumed_before_owner_delivery": true
|
||||
},
|
||||
"flex_auth_binary_sha256": "d05d90212da8d762effb2b1b547aed29ab704791646714773d3fb57721a9b937",
|
||||
"producer_input_sha256": {
|
||||
"policy_package.md": "657fa9312c9dcabee059ec31c89ee13396fe9aa54d852edc9e6559a74bbc139a",
|
||||
"registry_snapshot.json": "37fba44ec0e0fd9b1e17a05ad1b7e4f9bb6ec9e0fc0bb3133e8af2ac6f832d7c"
|
||||
},
|
||||
"exec_owner_scope": "Synthetic recipient/backend with real KeyCape, Approval Engine, Flex Auth and Secrets Engine CLI; not native custody or human approval proof",
|
||||
"status": "passed",
|
||||
"cleanup_complete": true,
|
||||
"finished_at": "2026-09-13T22:43:02.850912+00:00"
|
||||
}
|
||||
22
docs/evidence/2026-09-14-openrouter-live-pdp.json
Normal file
22
docs/evidence/2026-09-14-openrouter-live-pdp.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"image": "sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd",
|
||||
"pod": "flex-auth-secrets-engine-6467b96c6f-ncl5t",
|
||||
"scope": "PDP evaluation only; synthetic claim, no approval consume or OpenBao access",
|
||||
"checks": {
|
||||
"native_request_preserved": true,
|
||||
"missing_caller_refused": true,
|
||||
"wrong_caller_refused": true,
|
||||
"foreign_owner_representation_refused": true,
|
||||
"wrong_tenant_denied": true,
|
||||
"recipient_cannot_be_lifecycle_subject": true,
|
||||
"producer_approval_digest_pair": true,
|
||||
"changed_path_cannot_reuse_approval_digest": true,
|
||||
"changed_mount_cannot_reuse_approval_digest": true,
|
||||
"changed_repo_cannot_reuse_approval_digest": true,
|
||||
"other_deployment_specs_unchanged": true
|
||||
},
|
||||
"decision_id": "decision:e8bec2d72f7df007",
|
||||
"forward_stopped": true,
|
||||
"caller_token_handling": "10 minute audience-bound tokens held in memory only; expire at issuer, not revoked by dropping local references",
|
||||
"consumer_validation": "Actual secrets-engine validate_decision_envelope accepted the live response against the exact submission and v2 pin."
|
||||
}
|
||||
20
docs/evidence/2026-09-14-openrouter-native-plan.txt
Normal file
20
docs/evidence/2026-09-14-openrouter-native-plan.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Plan for lane 'openrouter-llm-connect' (stage=prod)
|
||||
decision: <none>
|
||||
stage role: secrets-engine-prod
|
||||
consumer policy: se-prod-openrouter-llm-connect
|
||||
consumer approle: se-prod-openrouter-llm-connect
|
||||
actions:
|
||||
[kv-mount-check] platform (type=kv-v2, management=existing, mutation=none)
|
||||
[policy] se-prod-openrouter-llm-connect (paths=platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets)
|
||||
[approle] se-prod-openrouter-llm-connect (token_policies=se-prod-openrouter-llm-connect, auth=approle, token_ttl=15m, token_max_ttl=30m, token_num_uses=8, secret_id_ttl=15m, secret_id_num_uses=1)
|
||||
|
||||
generated consumer policy (HCL):
|
||||
# Generated by secrets-engine for policy "se-prod-openrouter-llm-connect"
|
||||
|
||||
path "platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
|
@ -1,3 +1,15 @@
|
|||
> Current protocol correction (2026-09-14): follow claim → validated PDP Check →
|
||||
> CAS consume, as implemented in docs/approval-consumption.md. ActionAuthorization
|
||||
> is deferred and never ratified. The OpenRouter-specific current implementation,
|
||||
> live PDP receipt and remaining service/reader gates are in
|
||||
> docs/openrouter-native-access.md (SECRETS-WP-0010).
|
||||
|
||||
> Current protocol correction (2026-09-14): follow claim → validated PDP Check →
|
||||
> CAS consume, as implemented in docs/approval-consumption.md. ActionAuthorization
|
||||
> is deferred and never ratified. The OpenRouter-specific current implementation,
|
||||
> live PDP receipt and remaining service/reader gates are in
|
||||
> docs/openrouter-native-access.md (SECRETS-WP-0010).
|
||||
|
||||
# Native lane cutover (SECRETS-WP-0006-T05 / T06)
|
||||
|
||||
Status: procedure only. Live apply is still fail-closed until
|
||||
|
|
@ -44,7 +56,7 @@ Provenance CCR: `CCR-2026-0003` (existing workload lane only).
|
|||
|
||||
Required before OpenBao:
|
||||
|
||||
1. Canonical ActionAuthorization for `action=apply`, resource
|
||||
1. Live approval-claim, validated PDP Check and CAS consume for `action=apply`, resource
|
||||
`catalog:openrouter-llm-connect`, stage `prod`, exact policy/auth targets
|
||||
`se-prod-openrouter-llm-connect`.
|
||||
2. Successful approval-engine CAS consume of that request digest
|
||||
|
|
|
|||
82
docs/openrouter-native-access.md
Normal file
82
docs/openrouter-native-access.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# OpenRouter native preparation — 2026-09-14
|
||||
|
||||
SECRETS-WP-0010 implements the bounded owner packet from IR-WP-0004,
|
||||
message `cfab5355-b0f9-4868-b4e6-61ea42c54b0f`.
|
||||
|
||||
## Implemented
|
||||
|
||||
Every action request now includes a deep copy of the non-secret catalog target
|
||||
in `context.catalog_target`: custody mount/path, owning org/repo, declared fields,
|
||||
consumers, mount management, delivery modes/config/auth and workload delivery.
|
||||
The existing action, stage, purpose and human-control fields remain bound.
|
||||
Approval object identifiers are excluded so issuance does not change its own
|
||||
approval-free digest. Changed targets or token limits require a new approval;
|
||||
existing approvals created before this change cannot authorize new submissions.
|
||||
This binds proposed inputs; it does not turn catalog metadata into a grant.
|
||||
|
||||
Plans now expose all AppRole limits, including the secret-ID TTL and use count.
|
||||
The OpenRouter plan checks the existing mount and writes only the exact-path
|
||||
policy and AppRole `se-prod-openrouter-llm-connect`. Limits: token TTL 15m, maximum
|
||||
30m, secret-ID TTL 15m/single use, eight token uses. No KV mutation or ESO change.
|
||||
See the non-secret plan and claim-free request under `docs/evidence/2026-09-14-*`.
|
||||
Those are review inputs, not issued approvals; regenerate after configuring the
|
||||
actual recipient because its target binding changes.
|
||||
|
||||
`tools/openrouter_key_check.py` is the first proposed recipient. It performs one
|
||||
TLS-verified GET to the fixed endpoint, ignores proxy configuration, follows no
|
||||
redirects and makes no inference request. It emits only a fixed result and HTTP
|
||||
status; no provider fields, labels, response bodies, key values or exception text.
|
||||
The endpoint is OpenRouter's documented
|
||||
[current-key information API](https://openrouter.ai/docs/api/api-reference/api-keys/get-current-key).
|
||||
A successful check does not reconcile radar's existing spend reservation.
|
||||
|
||||
`docs/proposals/openrouter-key-check.yaml` is an inactive proposed catalog overlay.
|
||||
It retains the existing lane id/KV/policy/AppRole and ESO consumer, declares human
|
||||
control, and names the key-check recipient with a pending exec-owner binding.
|
||||
The active `catalog/openrouter-llm-connect.yaml` remains limited to its admitted
|
||||
llm-connect purpose. The proposal is outside the active catalog and pending exec
|
||||
refuses before consume/backend. Do not copy it into the active catalog until
|
||||
custody-owner admission and the exact protected recipient installation are reviewed.
|
||||
|
||||
## Verification
|
||||
|
||||
410 tests passed, including disposable OpenBao coverage. New tests cover changed
|
||||
path, mount, owner, consumer, limits, fields and delivery mode replay; frozen
|
||||
request inputs; complete plan limits; and sanitized key-check success/errors,
|
||||
redirect refusal, malformed/oversized responses and transport cleanup.
|
||||
The actual local KeyCape + Approval Engine + compiled Flex Auth exercise passed
|
||||
26 checks, including changed catalog target refusal before consume/backend and
|
||||
exact child isolation. Its human approval and backend are explicit synthetic
|
||||
fixtures, not native production evidence.
|
||||
|
||||
Flex Auth's dedicated native pin was promoted to Helm revision 4 with the existing
|
||||
published image `sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd`.
|
||||
Eleven live evaluation checks passed, including current replay/approval binding;
|
||||
other PDP deployment specs are unchanged. The separate local and live receipts
|
||||
state their scope and cleanup. This removes the outdated PDP image dependency.
|
||||
|
||||
## Remaining admitted execution sequence (SECRETS-WP-0010-T03)
|
||||
|
||||
1. Complete APPROVAL-WP-0002-T01/T03/T05 and its identity/audit dependencies;
|
||||
its declared namespace currently has no StatefulSet, pod or Service. Complete
|
||||
RPF-WP-0035-T06 / CCR-2026-0019 client-side reader admission for the secrets-engine
|
||||
approval-client. Keep secrets outside Git and use the existing attended
|
||||
procedure in `docs/approval-service-auth.md`; do not read the verifier's Secret.
|
||||
2. Have the custody owner admit this exact read-only recipient. Install and pin
|
||||
its Python runtime and script, exact argv, private working directory and fixed
|
||||
environment via `docs/exec-owner-binding.md`. Obtain the scoped attended
|
||||
platform authority; no standing bootstrap credential or sibling identity.
|
||||
3. Render the finalized request, use the evaluator-origin digest to create a
|
||||
declared-human-control approval, obtain the real human entry, observe claim,
|
||||
validate Check and CAS-consume before apply. `ActionAuthorization` is deferred,
|
||||
not a missing API to implement. CCR-2026-0003 is provenance, not this approval.
|
||||
4. Apply only the reviewed policy/AppRole. Verify correct-reader access, unrelated
|
||||
identity and unrelated path denials, all fields, role limits, native session
|
||||
revocation and llm-connect ESO/application health. No raw provider value in
|
||||
evidence. Obtain a separate exact exec approval for the pinned key check.
|
||||
5. Only after verified native readiness should SECRETS-WP-0006-T06 retire the
|
||||
interim route. Rollback removes only the new policy/AppRole; preserve KV/ESO.
|
||||
6. Trial delivery requires its own reviewed, pinned radar recipient/config and
|
||||
approval. Reconcile the existing USD 0.023712 reservation in campaign
|
||||
`ir-openrouter-pilot-20260913`; preserve the authorized USD 10 ceiling. This
|
||||
task did not execute the key check or spend campaign funds.
|
||||
87
docs/proposals/openrouter-key-check.yaml
Normal file
87
docs/proposals/openrouter-key-check.yaml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# PROPOSED reviewed-input template; not active catalog or credential admission.
|
||||
# SECRETS-WP-0010; retain the existing KV and AppRole names.
|
||||
id: openrouter-llm-connect
|
||||
kind: kv
|
||||
org: coulomb
|
||||
repo: llm-connect
|
||||
stage: prod
|
||||
description: >-
|
||||
OpenRouter provider credential used by llm-connect for activity-core. The
|
||||
proposed native recipient checks key authentication only; trial delivery
|
||||
requires a separate reviewed recipient and exact-action approval.
|
||||
|
||||
mount: platform
|
||||
path: workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
||||
mount_management: existing
|
||||
fields:
|
||||
- OPENROUTER_API_KEY
|
||||
|
||||
consumers:
|
||||
- name: llm-connect-activity-core
|
||||
auth: kubernetes
|
||||
claim: serviceaccount:external-secrets/external-secrets
|
||||
purpose: sync llm-connect-provider-secrets for the activity-core deployment
|
||||
- name: intelligence-radar-key-check
|
||||
auth: approle
|
||||
claim: catalog:openrouter-llm-connect
|
||||
purpose: IR-WP-0004 read-only OpenRouter key authentication check; no inference
|
||||
|
||||
workload_delivery:
|
||||
- mode: external-secrets
|
||||
owner: railiance-platform + llm-connect
|
||||
|
||||
delivery_modes: [exec-env, read-check]
|
||||
delivery_auth:
|
||||
method: approle
|
||||
management: engine
|
||||
policy_name: se-prod-openrouter-llm-connect
|
||||
role_name: se-prod-openrouter-llm-connect
|
||||
token_ttl: 15m
|
||||
token_max_ttl: 30m
|
||||
secret_id_ttl: 15m
|
||||
secret_id_num_uses: 1
|
||||
token_num_uses: 8
|
||||
|
||||
approval:
|
||||
model: ccr
|
||||
human_control: true
|
||||
purpose: IR-WP-0004 read-only OpenRouter key authentication check; no inference
|
||||
decision_ref: CCR-2026-0003
|
||||
notes: >-
|
||||
CCR-2026-0003 approves the existing workload lane. A live native AppRole
|
||||
apply remains gated by SECRETS-WP-0006-T05 review and operator approval.
|
||||
First native-cutover candidate (docs/native-lane-cutover.md); not an
|
||||
authorization to apply.
|
||||
|
||||
verification:
|
||||
positive: approved delivery AppRole can read OPENROUTER_API_KEY without printing it
|
||||
negative: unrelated token is denied on platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
||||
|
||||
risk:
|
||||
classification: high
|
||||
notes: provider key can incur spend and remains valid outside OpenBao until revoked at OpenRouter
|
||||
|
||||
rotation:
|
||||
owner: railiance-platform + llm-connect provider operator
|
||||
expectation: >-
|
||||
Mint a replacement at OpenRouter, update every live cluster copy, wait for
|
||||
ESO refresh, restart llm-connect, verify, and revoke the old provider key.
|
||||
ttl: provider-defined
|
||||
|
||||
deactivation:
|
||||
owner: railiance-platform + llm-connect
|
||||
expectation: >-
|
||||
Disable the front door and workload read path, remove materialized Secrets,
|
||||
stop consumers, and revoke the provider key at OpenRouter.
|
||||
|
||||
audit:
|
||||
evidence: CCR id, actor, exact path, field name, provider key identifier if non-secret, timestamps, and pass/fail only
|
||||
|
||||
delivery_config:
|
||||
exec_owner:
|
||||
status: pending
|
||||
owner: intelligence-radar-key-check
|
||||
reason: >-
|
||||
Pin tools/openrouter_key_check.py and its protected Python runtime, exact
|
||||
argv, private cwd and fixed environment after custody-owner admission.
|
||||
The pending binding refuses exec before consume or secret retrieval.
|
||||
|
|
@ -6,6 +6,7 @@ policy, lifetime and approval correspondence before consumption.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
|
|
@ -102,7 +103,22 @@ def build_action_request(
|
|||
"auth_targets": sorted(set(auth_targets)),
|
||||
},
|
||||
},
|
||||
"context": {"purpose": purpose},
|
||||
"context": {
|
||||
"purpose": purpose,
|
||||
# Bind the actual non-secret custody/delivery specification, not
|
||||
# just names which can be reused for a different backend target.
|
||||
# Approval identifiers are excluded: adding the newly issued
|
||||
# approval must not change its own approval-free binding.
|
||||
"catalog_target": copy.deepcopy({
|
||||
name: getattr(entry, name)
|
||||
for name in (
|
||||
"kind", "org", "repo", "mount", "path", "fields",
|
||||
"mount_management", "consumers", "delivery_modes",
|
||||
"delivery_auth", "delivery_config", "auth_capability",
|
||||
"workload_delivery",
|
||||
)
|
||||
}),
|
||||
},
|
||||
}
|
||||
)
|
||||
if human_control_required(entry):
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ def build_plan(entry: CatalogEntry, stage: str, *, decision_id: str = "") -> Pla
|
|||
"token_policies": policy_name,
|
||||
"auth": "approle",
|
||||
"token_ttl": entry.token_ttl,
|
||||
"token_max_ttl": entry.token_max_ttl,
|
||||
"secret_id_ttl": entry.secret_id_ttl,
|
||||
"token_num_uses": entry.token_num_uses,
|
||||
"secret_id_num_uses": entry.secret_id_num_uses,
|
||||
},
|
||||
),
|
||||
|
|
@ -114,6 +117,8 @@ def build_plan(entry: CatalogEntry, stage: str, *, decision_id: str = "") -> Pla
|
|||
"token_ttl": entry.delivery_token_ttl,
|
||||
"token_max_ttl": entry.delivery_token_max_ttl,
|
||||
"token_num_uses": entry.delivery_token_num_uses,
|
||||
"secret_id_ttl": entry.delivery_secret_id_ttl,
|
||||
"secret_id_num_uses": entry.delivery_secret_id_num_uses,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import copy
|
|||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from secrets_engine.catalog import validate_entry
|
||||
from tests.test_catalog import VALID
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -122,7 +123,7 @@ def test_real_expired_allow_refused():
|
|||
|
||||
|
||||
def test_request_builder_tenant_boundary():
|
||||
entry = SimpleNamespace(id="glas-primary", stage="prod")
|
||||
entry = validate_entry({**VALID, "id": "glas-primary", "stage": "prod"})
|
||||
args = dict(subject_id="secrets-engine", subject_type="service", purpose="rotation")
|
||||
request = build_action_request(entry, "rotate", **args)
|
||||
assert request["tenant"] == REQUEST_TENANT
|
||||
|
|
|
|||
61
tests/test_openrouter_binding.py
Normal file
61
tests/test_openrouter_binding.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""A named native lane must not hide a changed custody or delivery target."""
|
||||
import copy
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from secrets_engine.authorization import build_action_request, approval_binding_digest, validate_decision_envelope
|
||||
from secrets_engine.catalog import load_entry, validate_entry
|
||||
from secrets_engine.errors import DecisionError
|
||||
from secrets_engine.plan import build_plan
|
||||
from tests.test_action_authorization import _envelope
|
||||
|
||||
CATALOG = Path(__file__).resolve().parents[1] / "catalog/openrouter-llm-connect.yaml"
|
||||
|
||||
|
||||
def request(entry):
|
||||
return build_action_request(entry, "apply", subject_id="secrets-engine",
|
||||
subject_type="service", request_id="check:openrouter-apply", purpose="IR-WP-0004 native OpenRouter access",
|
||||
policy_targets=[entry.policy_name], auth_targets=[entry.role_name])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("change", ["path", "mount", "repo", "consumer", "ttl", "uses", "field", "delivery"])
|
||||
def test_old_approval_cannot_follow_changed_native_target(change):
|
||||
entry = load_entry(CATALOG)
|
||||
original = request(entry)
|
||||
data = copy.deepcopy(entry.raw)
|
||||
if change in {"path", "mount", "repo"}: data[change] += "-other"
|
||||
elif change == "consumer": data["consumers"][1]["name"] = "unrelated-recipient"
|
||||
elif change == "ttl": data["delivery_auth"]["token_max_ttl"] = "24h"
|
||||
elif change == "uses": data["delivery_auth"]["secret_id_num_uses"] = 0
|
||||
elif change == "field": data["fields"].append("OTHER_KEY")
|
||||
elif change == "delivery": data["delivery_modes"].append("exec-file")
|
||||
changed = request(validate_entry(data))
|
||||
assert approval_binding_digest(original) != approval_binding_digest(changed)
|
||||
with pytest.raises(DecisionError, match="submitted request digest"):
|
||||
validate_decision_envelope(_envelope(original), changed,
|
||||
accepted_policy_packages={"secrets-engine.catalog-lane.lifecycle"},
|
||||
accepted_policy_versions={"v2"})
|
||||
|
||||
|
||||
def test_issuing_approval_does_not_change_its_own_target():
|
||||
entry = load_entry(CATALOG)
|
||||
original = request(entry)
|
||||
data = copy.deepcopy(entry.raw)
|
||||
data["approval"]["authorization_id"] = "new-approval-object"
|
||||
assert approval_binding_digest(original) == approval_binding_digest(request(validate_entry(data)))
|
||||
entry.delivery_auth["token_max_ttl"] = "24h"
|
||||
assert original["context"]["catalog_target"]["delivery_auth"]["token_max_ttl"] == "30m"
|
||||
|
||||
|
||||
def test_openrouter_plan_exposes_all_limits_and_preserves_existing_custody():
|
||||
entry = load_entry(CATALOG)
|
||||
plan = build_plan(entry, "prod")
|
||||
assert [a.kind for a in plan.actions] == ["kv-mount-check", "policy", "approle"]
|
||||
assert plan.policy_name == plan.role_name == "se-prod-openrouter-llm-connect"
|
||||
assert entry.kv_data_path in plan.policy_hcl
|
||||
assert plan.actions[-1].detail == {
|
||||
"token_policies": plan.policy_name, "auth": "approle",
|
||||
"token_ttl": "15m", "token_max_ttl": "30m", "token_num_uses": 8,
|
||||
"secret_id_ttl": "15m", "secret_id_num_uses": 1,
|
||||
}
|
||||
54
tests/test_openrouter_key_check.py
Normal file
54
tests/test_openrouter_key_check.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location("openrouter_key_check", Path(__file__).resolve().parents[1] / "tools/openrouter_key_check.py")
|
||||
probe = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(probe)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status,body,result", [
|
||||
(200, b'{"data":{"label":"SYNTHETIC-KEY","usage":999}}', "authenticated"),
|
||||
(401, b'SYNTHETIC-KEY', "refused"),
|
||||
(302, b'SYNTHETIC-KEY', "refused"),
|
||||
(500, b'SYNTHETIC-KEY', "refused"),
|
||||
(200, b'bad SYNTHETIC-KEY', "check_failed"),
|
||||
(200, b'{"data":null}', "invalid_response"),
|
||||
(200, b'x' * (probe.MAX_BODY + 1), "invalid_response"),
|
||||
])
|
||||
def test_probe_is_one_fixed_read_and_never_returns_provider_content(status, body, result):
|
||||
calls = []
|
||||
class Connection:
|
||||
def __init__(self, host, timeout):
|
||||
assert (host, timeout) == ("openrouter.ai", 10)
|
||||
def request(self, method, path, headers):
|
||||
calls.append((method, path))
|
||||
assert headers["Authorization"] == "Bearer SYNTHETIC-KEY"
|
||||
def getresponse(self): return self
|
||||
def read(self, size):
|
||||
assert size == probe.MAX_BODY + 1
|
||||
return body[:size]
|
||||
def close(self): calls.append("closed")
|
||||
Connection.status = status
|
||||
output = probe.check("SYNTHETIC-KEY", connection_factory=Connection)
|
||||
assert output["result"] == result
|
||||
assert "SYNTHETIC-KEY" not in json.dumps(output)
|
||||
assert calls == [("GET", "/api/v1/key"), "closed"]
|
||||
|
||||
|
||||
def test_transport_error_is_sanitized_and_connection_closed():
|
||||
closed = []
|
||||
class Broken:
|
||||
def __init__(self, *a, **kw): pass
|
||||
def request(self, *a, **kw): raise OSError("SYNTHETIC-KEY")
|
||||
def close(self): closed.append(True)
|
||||
assert probe.check("SYNTHETIC-KEY", connection_factory=Broken) == {"result": "check_failed"}
|
||||
assert closed == [True]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["", "key\r\nInjected: value", "key with spaces", "non-ascii-ä"])
|
||||
def test_bad_input_never_opens_connection(key):
|
||||
def forbidden(*a, **kw): pytest.fail("must not connect")
|
||||
assert probe.check(key, connection_factory=forbidden) == {"result": "invalid_input"}
|
||||
|
|
@ -430,6 +430,21 @@ def exercise(args):
|
|||
assert not engine.claim("synthetic-owner-delivery")["consumed"] and not backend_calls
|
||||
else:
|
||||
raise AssertionError("substitute recipient accepted")
|
||||
from dataclasses import replace
|
||||
for name, changed_entry in {
|
||||
"path": replace(owner_entry, path=owner_entry.path + "-unrelated"),
|
||||
"mount": replace(owner_entry, mount=owner_entry.mount + "-unrelated"),
|
||||
"owner": replace(owner_entry, repo=owner_entry.repo + "-unrelated"),
|
||||
"limits": replace(owner_entry, delivery_auth={**owner_entry.delivery_auth, "token_max_ttl": "24h"}),
|
||||
}.items():
|
||||
with patch.object(cli, "get_entry", return_value=changed_entry):
|
||||
try:
|
||||
cli.cmd_exec(cfg, cli_args)
|
||||
except DecisionError:
|
||||
assert not engine.claim("synthetic-owner-delivery")["consumed"] and not backend_calls
|
||||
else:
|
||||
raise AssertionError("changed catalog target replay accepted: " + name)
|
||||
receipt["checks"]["catalog_" + name + "_change_refused_before_consume_backend"] = True
|
||||
original_env = raw["delivery_config"]["exec_owner"]["environment"]["LANG"]
|
||||
raw["delivery_config"]["exec_owner"]["environment"]["LANG"] = "C"
|
||||
try:
|
||||
|
|
|
|||
53
tools/openrouter_key_check.py
Normal file
53
tools/openrouter_key_check.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Pinned read-only recipient for native delivery; no model inference or retries.
|
||||
|
||||
Review and pin this script AND its Python runtime with delivery_config.exec_owner.
|
||||
The custody owner must admit this recipient before a real key is delivered.
|
||||
"""
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HOST = "openrouter.ai"
|
||||
PATH = "/api/v1/key"
|
||||
MAX_BODY = 65536
|
||||
|
||||
|
||||
def check(key, *, connection_factory=http.client.HTTPSConnection):
|
||||
if not key or not key.isascii() or any(ord(c) <= 32 or ord(c) == 127 for c in key):
|
||||
return {"result": "invalid_input"}
|
||||
connection = None
|
||||
try:
|
||||
# HTTPSConnection verifies TLS, ignores proxy env, and follows no redirects.
|
||||
connection = connection_factory(HOST, timeout=10)
|
||||
connection.request("GET", PATH, headers={"Authorization": "Bearer " + key, "Accept": "application/json"})
|
||||
response = connection.getresponse()
|
||||
if response.status != 200:
|
||||
return {"result": "refused", "http_status": response.status}
|
||||
body = response.read(MAX_BODY + 1)
|
||||
if len(body) > MAX_BODY:
|
||||
return {"result": "invalid_response"}
|
||||
payload = json.loads(body)
|
||||
if not isinstance(payload, dict) or not isinstance(payload.get("data"), dict):
|
||||
return {"result": "invalid_response"}
|
||||
# Do not serialize any provider field: labels can contain key prefixes,
|
||||
# error bodies can reflect credentials, and aggregate usage is not a
|
||||
# reconciliation receipt for radar's existing campaign reservation.
|
||||
return {"result": "authenticated", "http_status": 200}
|
||||
except (OSError, http.client.HTTPException, ValueError):
|
||||
return {"result": "check_failed"}
|
||||
finally:
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
|
||||
|
||||
def main():
|
||||
key = os.environ.pop("OPENROUTER_API_KEY", "")
|
||||
result = {"result": "invalid_arguments"} if len(sys.argv) != 1 else check(key)
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return 0 if result["result"] == "authenticated" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -710,9 +710,15 @@ priority: high
|
|||
state_hub_task_id: "a0a1dd92-d703-5a95-b488-d895f34d5cf7"
|
||||
```
|
||||
|
||||
Dependency: T01–T03, T05, T06 are done. T04 still waits on the durable
|
||||
ActionAuthorization serving path. Also requires explicit per-lane approvals
|
||||
and scoped production authority from railiance-platform.
|
||||
Dependency: T01–T03, T05, T06 are done. T04 implements claim/PDP Check/CAS consume;
|
||||
ActionAuthorization is deferred, not its serving prerequisite. Production still
|
||||
waits on the admitted Approval Engine identity/audit/service and client-reader
|
||||
path, explicit per-lane approval and scoped attended platform authority.
|
||||
|
||||
2026-09-14: SECRETS-WP-0010-T01/T02 implemented exact catalog-target binding and
|
||||
the proposed value-safe OpenRouter recipient. FLEX-WP-0026 promoted the dedicated
|
||||
PDP to revision 4 and verified the live replay join. Native admission/verification
|
||||
remains SECRETS-WP-0010-T03; no real key was retrieved and this task remains wait.
|
||||
|
||||
Resume `SECRETS-WP-0006-T05` lane by lane. Apply only the exact native policy and
|
||||
bounded AppRole, verify every field plus unrelated-consumer denial, confirm the
|
||||
|
|
|
|||
68
workplans/SECRETS-WP-0010-openrouter-native-access.md
Normal file
68
workplans/SECRETS-WP-0010-openrouter-native-access.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
id: SECRETS-WP-0010
|
||||
type: workplan
|
||||
title: "Native OpenRouter access for intelligence-radar"
|
||||
domain: infotech
|
||||
repo: secrets-engine
|
||||
status: blocked
|
||||
owner: codex
|
||||
topic_slug: netkingdom
|
||||
created: "2026-09-14"
|
||||
updated: "2026-09-14"
|
||||
related_workplans:
|
||||
- IR-WP-0004
|
||||
- FLEX-WP-0026
|
||||
- SECRETS-WP-0007
|
||||
- SECRETS-WP-0006
|
||||
---
|
||||
|
||||
Source request: intelligence-radar message cfab5355-b0f9-4868-b4e6-61ea42c54b0f.
|
||||
Implementation and execution procedure: `docs/openrouter-native-access.md`.
|
||||
|
||||
## Bind approval to actual native custody and delivery inputs
|
||||
|
||||
```task
|
||||
id: SECRETS-WP-0010-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Implemented context.catalog_target and complete plan limits. Changed paths,
|
||||
mounts, owners and token limits refuse replay before consume/backend against
|
||||
real local components. Existing exec-owner and human-control contracts retained.
|
||||
410 repository tests and 26 component checks passed. Receipts in docs/evidence.
|
||||
|
||||
## Prepare value-safe first recipient and exact native plan
|
||||
|
||||
```task
|
||||
id: SECRETS-WP-0010-T02
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Implemented the fixed read-only OpenRouter key-check script and synthetic tests.
|
||||
Inactive proposed overlay declares human control and a pending exact recipient;
|
||||
active llm-connect catalog admission is not broadened. Non-secret apply request
|
||||
and bounded plan are review artifacts, not runtime grants.
|
||||
|
||||
## Admit and verify real native delivery
|
||||
|
||||
```task
|
||||
id: SECRETS-WP-0010-T03
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Live residual from FLEX-WP-0026: the dedicated PDP is now current (revision 4,
|
||||
11 live checks). Actual delivery still requires APPROVAL-WP-0002-T01/T03/T05
|
||||
(identity/audit/service deployment), RPF-WP-0035-T06 / CCR-2026-0019 client-reader
|
||||
admission, exact installed recipient admission, real human approval/consume and
|
||||
scoped attended platform authority. No Approval Engine StatefulSet/pod/Service
|
||||
was present in its declared namespace at the 2026-09-14 inspection.
|
||||
|
||||
Then execute `docs/openrouter-native-access.md` steps: bounded native apply,
|
||||
positive/negative checks, ESO/app health, value-safe key check and session revoke.
|
||||
SECRETS-WP-0007-T04/T07 and SECRETS-WP-0006-T05/T06 remain wait; this workplan
|
||||
must not close them from synthetic evidence. Keep WARDEN-WP-0039-T03 and
|
||||
IR-WP-0004-T02 waiting until the native route passes. Trials require a separately
|
||||
bound recipient and the existing campaign/budget reconciliation.
|
||||
Loading…
Add table
Add a link
Reference in a new issue