Skip to content

Latest commit

 

History

History
193 lines (160 loc) · 10.3 KB

File metadata and controls

193 lines (160 loc) · 10.3 KB

Security Authority Proof

Runx receipts must explain the authority boundary without becoming a secret side channel. The compact policy projection lives under authority_proof and validates against runx.authority-proof.v1. The adapter-observed execution boundary is also copied into receipt.authority.enforcement before sealing, so it is part of the signed receipt body rather than unsigned read metadata.

Allowed public fields:

  • run_id, skill_name, and source_type
  • requested connected-auth scopes and whether the skill declared mutating work
  • scope admission status, granted scopes, grant id, and decision summary
  • provider, connection id, grant reference, and material_ref hash
  • typed execution-boundary observation and approval result
  • redaction policy status

Banned fields:

  • raw access tokens, refresh tokens, API keys, passwords, client secrets, and provider credential bodies
  • full private stdout or stderr bodies in public projections
  • ambient environment dumps or unbounded local command logs
  • unchecked provider output bodies in comments, public evidence, or ledgers

Credential material is represented by hashed opaque handles such as material_ref_hash. Receipt writers still hash stdout and stderr, and metadata is passed through the receipt redactor before signing. Hosted workers and local runners use the same authority_proof schema name; consuming repos add policy for source channels, assignees, and target repositories outside the core proof. Local secret handoff is owned by the declared skill-credential contract and the Rust CredentialDelivery boundary. Hosted provider execution continues to use the credential-broker contract and opaque handles. In both paths, secret values may cross only the trusted adapter/supervisor delivery channel, not authority proofs, receipts, invocation metadata, adapter observations, or public provider evidence. See Credential Resolution.

Ownership Boundary

The Rust AuthorityProof wire structs and ExecutionBoundaryObservation live in runx-contracts, which owns their portable JSON shape and generated schemas. runx-core alone owns the policy projection that constructs an authority proof from admission decisions. The runtime adapter alone owns the observed execution-boundary value and binds it into the receipt at seal time. Neither a skill nor an agent may self-attest that observation.

The local runner applies authority in this order:

  1. Manifest validation identifies the selected runner and its declared credential requirement.
  2. The credential resolver selects one profile, project binding, hosted handle, or declared workspace source and constructs a redacted delivery.
  3. Structural policy admission resolves the exact authority grant.
  4. The adapter delivers only the resolved environment and credential material, executes through the lane's canonical boundary, and redacts captured output.
  5. The signed receipt records the observed boundary, public observations, and output hashes without raw material.

Provider-Permission Grants

provider_permission graph policy may declare required scopes, an expected grant id, and the authority verb. It must not declare granted_scopes; granted scopes come only from operator-carried runtime grant evidence.

Legacy provider-permission and MCP host steps fail closed unless the operator supplies:

  • RUNX_PROVIDER_PERMISSION_GRANT_ID
  • RUNX_PROVIDER_PERMISSION_GRANTED_SCOPES, encoded as a JSON array of exact capability strings (for example ["repo.read","issues.write"])

Native provider.read and provider.mutate steps use the same evidence model without requiring that setup in the common Connect path. They authenticate the operator, read active grant metadata from Connect, and select the unique grant whose provider and authoritative scopes cover the declared step. The selection is cached for the run. No provider token or credential body crosses into the skill. A host-injected native grant is a complete evidence tuple: the two variables above plus RUNX_PROVIDER_PERMISSION_PRINCIPAL_REF. Partial tuples never report ready.

The native boundary can also require exact expected_result identity fields and project only declared result_fields before the result enters a receipt. This prevents a correctly scoped call from being mistaken for the wrong resource and keeps undeclared secret-adjacent material out of skill output.

When more than one active grant matches, resolution fails as ambiguous. Set RUNX_PROVIDER_PERMISSION_GRANT_ID to select one; Runx then reads that grant's current scopes from Connect. A host may inject the complete three-variable tuple to carry already-resolved native grant evidence without a discovery call.

When a provider-permission effect is admitted, the sealed step receipt records the operator grant as a typed runx:grant:* reference under receipt.authority.grant_refs. The grant id is evidence of the authority that admitted the effect; it is not a credential body and does not carry provider token material.

Payment Aggregate Spend Caps

Spend-class payment authority must carry an aggregate cap (max_per_run_units or max_per_period_units) in addition to any per-call cap. Both aggregate caps are enforced by the runtime spend ledger.

Per-run: each run's reserved spend is bounded by the smaller of the two declared caps, because a run never spans more than one period.

Per-period: when the authority also declares a period of daily, weekly, or monthly, every spend is additionally reserved against a durable calendar-window ledger in the effect state file (RUNX_EFFECT_STATE_PATH or <receipt dir>/effect-state.json), so the cap holds across runs inside one UTC window. An unrecognized period value fails closed at admission instead of becoming an unenforced annotation. A period cap declared without a period is enforced only as the run-level clamp, and the period ledger only exists when an effect state path is configured — operators who want cross-run spend bounds must configure a stable state path.

Period ledgers are bounded during the same locked state transaction that records the reservation. For each family/authority/currency/period tuple, the runtime retains at least the active reservation window and the immediately previous window, keeps any newer windows already present, and prunes older period-ledger rows. Idempotency entries, finality records, finality events, consumed capabilities, and run-spend ledgers are not pruned by this retention pass. Out-of-order reservations remain safe because retention is computed relative to the reservation being recorded, not relative to the newest window currently present in the file.

When a payment effect is admitted, the sealed step receipt records authority evidence under receipt.authority.grant_refs: the admitted payment authority reference and the spend-capability reference. Replay receipts preserve those same authority references so a replayed sealed effect remains verifiable against the same admitted authority boundary.

Payment supervisor proofs bind the original settlement evidence through evidence_digest. Rebinding a stored proof to a re-sealed receipt first re-verifies that the stored evidence still hashes to the sealed digest, so evidence altered after issuance is rejected instead of silently re-blessed.

Offline Receipt Verification

runx verify [receipt-id] [--receipt-dir dir] [--receipt <path|->] [--json] re-checks sealed receipts from disk with no runtime or network dependency: canonical body digests, content-addressed ids, linked-tree parent/child integrity, scope adherence for privileged effects, and — when RUNX_RECEIPT_VERIFY_KID and RUNX_RECEIPT_VERIFY_ED25519_PUBLIC_KEY_BASE64 are set — production Ed25519 signatures against the operator-trusted key. When those explicit verifier variables are absent, a complete RUNX_RECEIPT_SIGN_* identity supplies its own public verifier; explicit verifier configuration always takes precedence. This lets the signing operator verify its own local store without duplicating key configuration while independent verifiers need only the public key. Store mode groups receipts into trees by lineage; a chain that points at a receipt missing from the store is reported as incomplete and fails verification. Single-receipt mode emits one runx.verify_verdict.v1 JSON verdict suitable for hosted notaries and other embedding surfaces. Because a single document cannot prove tree membership, lineage is reported as unverified without failing an otherwise valid receipt. The command exits non-zero on invalid receipts, so it can gate automation.

fixtures/receipt-verify/ is the conformance corpus for machine consumers. Every embedding surface that claims to verify a runx receipt must replay those fixtures through the pinned runx binary and match the expected verdicts instead of carrying a second verifier implementation in another language.

Scope adherence is intentionally pure and offline. Any act carrying typed EffectEvidence without corresponding receipt.authority.grant_refs produces EffectGrantEvidenceMissing, fails verification, and exits non-zero. This is the boundary between a signed activity log and a governance proof: the receipt must show both the privileged effect and the operator-granted authority that admitted it.

Operator Authority Diagnostics

runx doctor authority [--json] gives operators a redacted authority readiness view before exercising privileged effects. It reports:

  • receipt signer readiness, naming RUNX_RECEIPT_SIGN_KID, RUNX_RECEIPT_SIGN_ED25519_SEED_BASE64, and RUNX_RECEIPT_SIGN_ISSUER_TYPE
  • receipt verification readiness and whether it resolves from the explicit RUNX_RECEIPT_VERIFY_* pair or the complete signing identity
  • the resolved effect-state path when configured
  • the consequence when RUNX_EFFECT_STATE_PATH is unset: cross-run spend caps, payment idempotency, and effect replay recovery are not durable without a configured state path
  • provider-permission grant readiness, reporting either authenticated Connect discovery or the complete host-injected RUNX_PROVIDER_PERMISSION_GRANT_ID, JSON-array RUNX_PROVIDER_PERMISSION_GRANTED_SCOPES, and RUNX_PROVIDER_PERMISSION_PRINCIPAL_REF path

The diagnostic may show key ids and resolved filesystem paths. It must not show signing seeds, public key material, provider scope values, grant ids, or credential bodies.