secrets-engine's first live request rejected our allow: binding. request_digest is computed over material they never sent, because we enrich subject and resource from the registry before hashing. Answering that meant reading the enrichment path, which had a worse defect in it. Enrichment was additive-if-absent — addAttribute wrote a registry value only where the request had no value for that key. So where a caller supplied a key, the caller's value won and the registry's never applied. Every registry ceiling and allowlist was advisory. Verified against the shipped ops-warden package, each one added key on an otherwise-denied request: max_ttl_hours: 99 registry says 8 -> allowed a 12h certificate allowed_principals registry allowlist -> disallowed_principal bypassed allowed_subjects registry allowlist -> unknown_subject bypassed The third is the one to read twice: a subject the registry does not know authorized itself by naming itself in the allowlist it was being checked against. Not remotely reachable today — the PEP builds the CheckRequest, ops-warden sends no resource.attributes, and enforce admits one identity. It is a defence-in-depth failure: any path that lets attacker-influenced data into a CheckRequest field became a full policy bypass rather than a bounded input problem. Callers sending resource.attributes is not hypothetical; secrets-engine does it on every request. Registry facts now win, and diagnostics.registry_overrode names every displaced key, because a registry that silently discards a contradicting claim hides that a caller asserted authority it did not have. subject.type is carved out, and the reason is a finding of its own. Making the registry win there denied every secrets-engine allow: the registry's type is CARING vocabulary (Human, Agent, Automation, Service) and the request's is the protected system's actor vocabulary (service, adm, agt, atm). Two fields sharing a name; substituting one for the other is translation rather than identity, which GH-DEC-2026-008 ruled against. Note what surfaced it — the registry's type had been dead data since the field existed, because the caller's value always won. Also publishes binding.submitted_request_digest, over the request exactly as sent. request_digest was published as the consumer replay test and cannot be one. Nothing is lost hashing the pre-enrichment form: enrichment is a function of the request and the snapshot, and registry_snapshot_digest already pins the snapshot. Existing pins do not move. All three replay fixtures' request_digest and approval_binding_digest values are byte-identical — those requests contradict no registry fact. A field to add, not a value to correct. Regression tests verified failing against the old behaviour before being kept. FLEX-DEC-2026-012; FLEX-WP-0025 carries the residual, that a policy still cannot tell a fact from an assertion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aQMM1dPXaPiXVn6DwwtLd Assistant: claude-code Assistant-Model: opus Assistant-Process: 715613@bnt-lap001 Assistant-Session: fabd95c1-4c9e-4080-8849-8707ae025f80
279 lines
7.9 KiB
JSON
279 lines
7.9 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://flex-auth.netkingdom/schemas/decision_envelope.schema.json",
|
|
"title": "DecisionEnvelope",
|
|
"description": "Published flex-auth decision-record contract (flex-auth.decision-record.v1). This is the PDP's output artifact under security-layer-model_v0.7 \u00a717.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"id",
|
|
"effect",
|
|
"resource",
|
|
"subject",
|
|
"provenance"
|
|
],
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"contract_version": {
|
|
"const": "flex-auth.decision-record.v1"
|
|
},
|
|
"request_id": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"effect": {
|
|
"enum": [
|
|
"allow",
|
|
"deny",
|
|
"redact",
|
|
"audit_only",
|
|
"not_applicable"
|
|
]
|
|
},
|
|
"reason": {
|
|
"type": "string"
|
|
},
|
|
"matched_policy_version": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"matched_rule": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"resource": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/resource_ref"
|
|
},
|
|
"subject": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/subject_ref"
|
|
},
|
|
"binding": {
|
|
"$ref": "#/$defs/decision_binding"
|
|
},
|
|
"lifetime": {
|
|
"$ref": "#/$defs/lifetime"
|
|
},
|
|
"obligations": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/$defs/obligation"
|
|
}
|
|
},
|
|
"diagnostics": {
|
|
"type": "object",
|
|
"additionalProperties": true
|
|
},
|
|
"provenance": {
|
|
"$ref": "#/$defs/provenance"
|
|
},
|
|
"caring": {
|
|
"$ref": "#/$defs/caring_decision_metadata"
|
|
}
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": {
|
|
"effect": {
|
|
"const": "allow"
|
|
}
|
|
},
|
|
"required": [
|
|
"effect"
|
|
]
|
|
},
|
|
"then": {
|
|
"required": [
|
|
"lifetime"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"$defs": {
|
|
"decision_binding": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"subject",
|
|
"action",
|
|
"resource",
|
|
"request_digest"
|
|
],
|
|
"properties": {
|
|
"tenant": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"subject": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/subject_ref"
|
|
},
|
|
"action": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"resource": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/check_request.schema.json#/$defs/resource_ref"
|
|
},
|
|
"context": {
|
|
"type": "object",
|
|
"additionalProperties": true
|
|
},
|
|
"request_digest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
|
"description": "Digest over the EVALUATED request, after the evaluator overlays registry facts onto subject and resource. It pins what the decision was a function of and makes the decision replayable by flex-auth for audit. It is NOT consumer-computable and must not be used as the consumer's replay test - use submitted_request_digest. With provenance.registry_snapshot_digest it identifies the evaluated request completely."
|
|
},
|
|
"approval_binding_digest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
|
"description": "Present only when the request carried an approval claim on context.approval. The request digest computed with that claim removed from context, so it is stable across attaching the claim and can therefore be named by an approval's pdp_digest recorded at issue time (GH-DEC-2026-008, FLEX-DEC-2026-007). Compare claim.binding.pdp_digest to THIS value, never to request_digest: a claim can never equal the digest of the request that carries it. Not a replay identity -- two requests differing only in which approval was presented share this value and their decisions do not. request_digest remains the replay identity."
|
|
},
|
|
"submitted_request_digest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
|
"description": "RequestDigest over the request exactly as the consumer sent it, before the evaluator overlays registry facts. This is the digest a PEP compares for security-layer-model 6.4 obligation 2; request_digest is over the enriched request and is not consumer-computable. See docs/request-enrichment.md (FLEX-DEC-2026-012)."
|
|
}
|
|
}
|
|
},
|
|
"obligation": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"type"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"parameters": {
|
|
"type": "object",
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
},
|
|
"lifetime": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"kind",
|
|
"expires_at"
|
|
],
|
|
"properties": {
|
|
"kind": {
|
|
"enum": [
|
|
"ttl"
|
|
]
|
|
},
|
|
"ttl": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"not_before": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"expires_at": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
}
|
|
}
|
|
},
|
|
"provenance": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"evaluator",
|
|
"mode"
|
|
],
|
|
"properties": {
|
|
"evaluator": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"mode": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"policy_package": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"policy_version": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"policy_package_digest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
},
|
|
"registry_snapshot_digest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
},
|
|
"directory_etag": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"input_claim_digests": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
}
|
|
},
|
|
"decision_time": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
}
|
|
}
|
|
},
|
|
"caring_decision_metadata": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"profile"
|
|
],
|
|
"properties": {
|
|
"profile": {
|
|
"const": "caring-0.4.0-rc2"
|
|
},
|
|
"descriptor": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json"
|
|
},
|
|
"restrictions_evaluated": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/restriction"
|
|
},
|
|
"uniqueItems": true
|
|
},
|
|
"exposure_modes": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_mode"
|
|
},
|
|
"uniqueItems": true
|
|
},
|
|
"derived_capabilities": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/derived_capability"
|
|
}
|
|
},
|
|
"conformance_findings": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/conformance_finding"
|
|
}
|
|
},
|
|
"exposure_event": {
|
|
"$ref": "https://flex-auth.netkingdom/schemas/caring_access_descriptor.schema.json#/$defs/exposure_event"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|