feat(casan): establish assurance kernel and harden control plane

This commit is contained in:
thanhnv
2026-08-02 23:24:51 +07:00
parent 8b477f3800
commit 5745519126
51 changed files with 4076 additions and 180 deletions
@@ -0,0 +1,78 @@
{
"schema_version": "1.0.0",
"description": "Deterministic action classes and minimum risk floors shared by CASAN runtimes.",
"risk_order": ["low", "medium", "high", "critical"],
"classes": {
"read_only": {"risk_floor": "low", "side_effect_level": "none", "requires_approval": false, "actor_required": false, "evidence_required": false},
"write": {"risk_floor": "medium", "side_effect_level": "write", "requires_approval": false, "actor_required": false, "evidence_required": true},
"delete": {"risk_floor": "high", "side_effect_level": "destructive", "requires_approval": true, "actor_required": true, "evidence_required": true},
"database_mutation": {"risk_floor": "high", "side_effect_level": "write", "requires_approval": true, "actor_required": true, "evidence_required": true},
"migration": {"risk_floor": "high", "side_effect_level": "write", "requires_approval": true, "actor_required": true, "evidence_required": true},
"deployment": {"risk_floor": "high", "side_effect_level": "external", "requires_approval": true, "actor_required": true, "evidence_required": true},
"release": {"risk_floor": "high", "side_effect_level": "external", "requires_approval": true, "actor_required": true, "evidence_required": true},
"credential_access": {"risk_floor": "high", "side_effect_level": "sensitive_read", "requires_approval": true, "actor_required": true, "evidence_required": true},
"identity_permission_modification": {"risk_floor": "high", "side_effect_level": "write", "requires_approval": true, "actor_required": true, "evidence_required": true},
"external_network_side_effect": {"risk_floor": "medium", "side_effect_level": "external", "requires_approval": true, "actor_required": true, "evidence_required": true},
"infrastructure_modification": {"risk_floor": "high", "side_effect_level": "external", "requires_approval": true, "actor_required": true, "evidence_required": true},
"unknown": {"risk_floor": "high", "side_effect_level": "unknown", "requires_approval": true, "actor_required": true, "evidence_required": true}
},
"action_aliases": {
"agent_step": "read_only",
"read": "read_only",
"search": "read_only",
"write": "write",
"write_code": "write",
"write_file": "write",
"delete": "delete",
"delete_file": "delete",
"db_write": "database_mutation",
"database_mutation": "database_mutation",
"migration": "migration",
"migrate": "migration",
"deploy": "deployment",
"deployment": "deployment",
"launch": "deployment",
"release": "release",
"credential_access": "credential_access",
"secret_access": "credential_access",
"permission_change": "identity_permission_modification",
"identity_change": "identity_permission_modification",
"external_api": "external_network_side_effect",
"infrastructure_change": "infrastructure_modification"
},
"tool_aliases": {
"read": "read_only",
"grep": "read_only",
"glob": "read_only",
"search": "read_only",
"view": "read_only",
"codegraph_search": "read_only",
"codegraph_node": "read_only",
"codegraph_context": "read_only",
"edit": "write",
"write": "write",
"multiedit": "write",
"notebookedit": "write",
"apply_patch": "write",
"str_replace_editor": "write",
"create_file": "write",
"update_file": "write",
"delete_file": "delete",
"webfetch": "external_network_side_effect",
"web_fetch": "external_network_side_effect",
"browser": "external_network_side_effect",
"sendmessage": "external_network_side_effect"
},
"command_patterns": [
{"pattern": "(^|\\s)(kubectl|helm)(\\s|$).*(apply|delete|upgrade|install)|(^|\\s)terraform\\s+(apply|destroy)", "class": "infrastructure_modification"},
{"pattern": "(^|\\s)(deploy|deployment|release)(\\s|$)|git\\s+push", "class": "deployment"},
{"pattern": "(^|\\s)(prisma|alembic|flyway|liquibase|rails)\\s+.*(migrat|upgrade)|\\bmigration\\b", "class": "migration"},
{"pattern": "\\b(delete|insert|update|alter|drop|truncate)\\s+(from|into|table|database)|\\b(db_write|database_mutation)\\b", "class": "database_mutation"},
{"pattern": "(^|\\s)(rm|rmdir|unlink)\\s|delete_file", "class": "delete"},
{"pattern": "(\\.ssh/|id_rsa|id_ed25519|\\.aws/credentials|\\.env($|\\s)|secret|credential|api[_-]?key)", "class": "credential_access"},
{"pattern": "\\b(iam|chmod|chown|setfacl|role|permission)\\b.*\\b(add|create|delete|grant|modify|remove|set|update)\\b", "class": "identity_permission_modification"},
{"pattern": "(^|\\s)(curl|wget|scp|sftp|ssh|nc|ncat)\\s", "class": "external_network_side_effect"},
{"pattern": "(^|\\s)(cp|mv|mkdir|touch|tee|sed\\s+-i)\\s|(^|\\s)(npm|pnpm|yarn|pip|cargo|go)\\s+(install|add|get)\\b|(^|\\s)(cat|printf|echo).*(>|>>)", "class": "write"},
{"pattern": "^\\s*(ls|pwd|cat|head|tail|wc|rg|grep|find|stat|git\\s+(status|diff|log|show))\\b", "class": "read_only"}
]
}
+25
View File
@@ -0,0 +1,25 @@
"""Framework-independent CASAN Assurance Kernel contracts and policy primitives."""
from .adapters import AgenticBridgeAdapter, NativeHarnessAdapter
from .contracts import CONTRACT_VERSION, validate_bundle
from .policy import (
classify_action,
evaluate_failure_policy,
evaluate_h2_gate,
evaluate_registry_configuration,
evaluate_risk,
evaluate_trust_capabilities,
)
__all__ = [
"AgenticBridgeAdapter",
"CONTRACT_VERSION",
"NativeHarnessAdapter",
"classify_action",
"evaluate_failure_policy",
"evaluate_h2_gate",
"evaluate_registry_configuration",
"evaluate_risk",
"evaluate_trust_capabilities",
"validate_bundle",
]
+194
View File
@@ -0,0 +1,194 @@
"""Compatibility adapters from existing runtimes into the canonical contract."""
from __future__ import annotations
from typing import Any, Mapping
from .contracts import CONTRACT_VERSION, actor, evidence_digest, outcome, utc_now
from .policy import evaluate_risk
class NativeHarnessAdapter:
runtime_type = "casan-native-harness"
def map_execution(self, event: Mapping[str, Any]) -> dict[str, Any]:
run_id = str(event.get("run_id") or event.get("trace_id") or "")
actor_id = str(event.get("actor") or "unidentified")
action_name = str(event.get("action") or "agent_step")
mode = str(event.get("mode") or "enforce")
risk = evaluate_risk(
action=action_name,
tool=str(event.get("tool") or action_name),
resource=str(event.get("resource") or ""),
command=str(event.get("command") or ""),
actor=actor_id if actor_id != "unidentified" else "",
environment=str(event.get("environment") or "development"),
)
execution_status = str(event.get("execution_status") or "unknown")
assurance_status = str(event.get("assurance_status") or "unknown")
certification = str(event.get("certification_status") or "non_certified")
return _bundle(
run_id=run_id,
runtime_type=self.runtime_type,
runtime_version=str(event.get("runtime_version") or "legacy-compatible"),
mode=mode,
actor_id=actor_id,
action_name=action_name,
risk=risk,
execution_status=execution_status,
assurance_status=assurance_status,
certification=certification,
event=event,
)
class AgenticBridgeAdapter:
runtime_type = "agentic-bridge"
def map_execution(self, event: Mapping[str, Any]) -> dict[str, Any]:
run_id = str(event.get("trace_id") or event.get("run_id") or "")
actor_id = str(event.get("actor") or "unidentified")
action_name = str(event.get("action") or event.get("last_action") or "agent_step")
mode = str(event.get("mode") or event.get("hook_trust_mode") or "observe")
risk = evaluate_risk(
action=action_name,
tool=str(event.get("tool") or event.get("last_tool") or action_name),
resource=str(event.get("resource") or ""),
command=str(event.get("command") or ""),
actor=actor_id if actor_id != "unidentified" else "",
environment=str(event.get("environment") or "development"),
)
execution_status = str(event.get("execution_status") or "unknown")
assurance_status = str(event.get("assurance_status") or "unknown")
certification = "certified" if event.get("certified") else "non_certified"
return _bundle(
run_id=run_id,
runtime_type=self.runtime_type,
runtime_version=str(event.get("adapter_version") or "legacy-compatible"),
mode=mode,
actor_id=actor_id,
action_name=action_name,
risk=risk,
execution_status=execution_status,
assurance_status=assurance_status,
certification=certification,
event=event,
)
def _bundle(
*,
run_id: str,
runtime_type: str,
runtime_version: str,
mode: str,
actor_id: str,
action_name: str,
risk: Mapping[str, Any],
execution_status: str,
assurance_status: str,
certification: str,
event: Mapping[str, Any],
) -> dict[str, Any]:
step_id = str(event.get("step_id") or f"{run_id}:1")
correlation_id = str(event.get("correlation_id") or run_id)
raw_evidence = list(event.get("evidence") or [])
claim_id = f"{run_id}:assurance-claim"
evidence = []
for index, item in enumerate(raw_evidence, 1):
raw = item if isinstance(item, Mapping) else {"detail": str(item)}
canonical_evidence = {
"evidence_id": str(raw.get("evidence_id") or f"{run_id}:evidence:{index}"),
"claim_refs": [claim_id],
"producer_identity": runtime_type,
"timestamp": raw.get("at") or raw.get("timestamp") or utc_now(),
"artifact_ref": str(raw.get("artifact_ref") or f"inline:{run_id}:{index}"),
"validation_status": "valid" if raw.get("decision") in {"allow", "pass", "passed", "certified"} else "unverified",
"retention_class": str(raw.get("retention_class") or "runtime_assurance"),
"category": str(raw.get("category") or "runtime_control"),
}
canonical_evidence["integrity"] = {
"algorithm": "sha256",
"digest": evidence_digest(canonical_evidence),
"source_content_hash": raw.get("content_hash"),
}
evidence.append(canonical_evidence)
policy_decisions = []
for item in list(event.get("policy_decisions") or []):
if not isinstance(item, Mapping):
continue
decision = str(item.get("decision") or "observe_only")
if decision not in {"allow", "deny", "quarantine", "require_approval", "transform", "observe_only"}:
decision = "observe_only"
policy_decisions.append({
"policy_id": str(item.get("policy_id") or "casan.compatibility.policy"),
"policy_version": str(item.get("policy_version") or item.get("schema_version") or CONTRACT_VERSION),
"decision": decision,
"reason_codes": list(item.get("reason_codes") or [item.get("reason_code") or "compatibility_mapping"]),
"effective_risk": str(item.get("effective_risk") or "high"),
"input_facts": dict(item),
"enforcement_point": str(item.get("enforcement_path") or runtime_type),
"timestamp": str(item.get("timestamp") or utc_now()),
"decision_engine_identity": "casan-assurance-kernel",
"evidence_ref": evidence[0]["evidence_id"] if evidence else None,
})
result = outcome(execution_status, assurance_status, certification)
return {
"schema_version": CONTRACT_VERSION,
"extension_namespace": f"casan.runtime.{runtime_type}",
"run": {
"run_id": run_id,
"parent_run_id": event.get("parent_run_id"),
"tenant": str(event.get("tenant") or "default"),
"project": str(event.get("project") or event.get("project_id") or "default"),
"environment": str(event.get("environment") or "development"),
"runtime": {"type": runtime_type, "version": runtime_version},
"mode": mode,
"requested_goal": str(event.get("requested_goal") or ""),
"started_at": event.get("started_at") or event.get("timestamp") or utc_now(),
"completed_at": event.get("completed_at") or utc_now(),
"status": execution_status,
"risk_summary": dict(risk),
"evidence_manifest_ref": event.get("evidence_manifest_ref"),
"correlation_id": correlation_id,
"outcome": result,
},
"actors": [actor(actor_id, "agent" if runtime_type == "casan-native-harness" else "runtime", runtime_type, "verified" if actor_id != "unidentified" else "unverified", event.get("authentication_evidence_ref"))],
"steps": [{
"step_id": step_id,
"run_id": run_id,
"parent_step_id": event.get("parent_step_id"),
"sequence": int(event.get("sequence") or 1),
"actor_ref": actor_id,
"action": {"class": risk["action_class"], "name": action_name, "tool": event.get("tool"), "side_effect_level": risk["side_effect_level"]},
"resource": event.get("resource"),
"input_context_refs": list(event.get("input_context_refs") or []),
"policy_decisions": policy_decisions,
"tool_invocation": event.get("tool_invocation"),
"verification_results": list(event.get("verification_results") or []),
"evidence_refs": [item["evidence_id"] for item in evidence],
"outcome": result,
"started_at": event.get("started_at") or event.get("timestamp") or utc_now(),
"completed_at": event.get("completed_at") or utc_now(),
}],
"runtime_capabilities": {
"canCancel": False, "canPause": False, "canResume": False,
"canRollback": False, "canReduceAuthority": False,
"canQuarantine": runtime_type == "agentic-bridge",
},
"claims": [{
"claim_id": claim_id,
"statement": "CASAN evaluated the runtime assurance outcome",
"evidence_refs": [item["evidence_id"] for item in evidence],
"validation_status": "validated" if assurance_status == "passed" else "insufficient",
}],
"evidence": evidence,
"trace_links": [
{"type": "parent_to_child_execution", "from": run_id, "to": step_id},
*[
{"type": "evidence_to_outcome", "from": item["evidence_id"], "to": claim_id}
for item in evidence
],
],
"extensions": {f"casan.runtime.{runtime_type}": dict(event.get("extensions") or {})},
}
+172
View File
@@ -0,0 +1,172 @@
"""Canonical CASAN wire-contract builders and lightweight validation."""
from __future__ import annotations
import hashlib
import json
from datetime import datetime, timezone
from typing import Any, Mapping
CONTRACT_VERSION = "1.0.0"
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def actor(actor_id: str, actor_type: str, issuer: str, trust_level: str, auth_evidence_ref: str | None = None) -> dict[str, Any]:
return {
"actor_id": actor_id,
"actor_type": actor_type,
"issuer": issuer,
"trust_level": trust_level,
"authentication_evidence_ref": auth_evidence_ref,
}
def outcome(execution: str, assurance: str, certification: str, business: str = "not_evaluated") -> dict[str, str]:
return {
"execution_result": execution,
"assurance_result": assurance,
"certification_result": certification,
"business_result": business,
}
def evidence_digest(evidence: Mapping[str, Any]) -> str:
"""Digest the claim-bearing evidence metadata, excluding its digest field."""
material = {
key: evidence.get(key)
for key in (
"evidence_id", "claim_refs", "producer_identity", "timestamp",
"artifact_ref", "validation_status", "retention_class", "category",
)
}
payload = json.dumps(material, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def verify_evidence_integrity(evidence: Mapping[str, Any]) -> bool:
integrity = evidence.get("integrity")
if not isinstance(integrity, Mapping) or integrity.get("algorithm") != "sha256":
return False
digest = integrity.get("digest")
return isinstance(digest, str) and digest == evidence_digest(evidence)
def validate_bundle(bundle: Mapping[str, Any]) -> list[str]:
"""Return deterministic contract errors; an empty list means valid.
JSON Schema is canonical. This small validator keeps runtime adapters stdlib-
only and catches the invariants needed at enforcement boundaries.
"""
errors: list[str] = []
if bundle.get("schema_version") != CONTRACT_VERSION:
errors.append("unsupported_schema_version")
run = bundle.get("run")
if not isinstance(run, Mapping):
errors.append("missing_execution_run")
return errors
for key in ("run_id", "runtime", "mode", "status", "correlation_id", "outcome"):
if not run.get(key):
errors.append(f"run.{key}_required")
actors = bundle.get("actors")
actor_ids: set[object] = set()
if not isinstance(actors, list) or not actors:
errors.append("actors_required")
else:
actor_ids = {item.get("actor_id") for item in actors if isinstance(item, Mapping)}
if "" in actor_ids or None in actor_ids:
errors.append("actor_identity_required")
steps = bundle.get("steps")
if not isinstance(steps, list):
errors.append("steps_required")
else:
for index, step in enumerate(steps):
if not isinstance(step, Mapping):
errors.append(f"steps.{index}_invalid")
continue
for key in ("step_id", "run_id", "sequence", "actor_ref", "action", "outcome"):
if step.get(key) is None:
errors.append(f"steps.{index}.{key}_required")
if step.get("actor_ref") not in actor_ids:
errors.append(f"steps.{index}.actor_ref_unknown")
evidence = bundle.get("evidence")
evidence_ids: set[object] = set()
if not isinstance(evidence, list):
errors.append("evidence_required")
else:
for index, item in enumerate(evidence):
if not isinstance(item, Mapping):
errors.append(f"evidence.{index}_invalid")
continue
evidence_ids.add(item.get("evidence_id"))
if not verify_evidence_integrity(item):
errors.append(f"evidence.{index}.integrity_invalid")
claims = bundle.get("claims")
claim_ids = {
item.get("claim_id") for item in claims or [] if isinstance(item, Mapping)
} if isinstance(claims, list) else set()
if isinstance(claims, list):
for index, claim in enumerate(claims):
if not isinstance(claim, Mapping):
errors.append(f"claims.{index}_invalid")
continue
for evidence_ref in claim.get("evidence_refs") or []:
if evidence_ref not in evidence_ids:
errors.append(f"claims.{index}.evidence_ref_unknown")
if isinstance(evidence, list):
for index, item in enumerate(evidence):
if isinstance(item, Mapping):
for claim_ref in item.get("claim_refs") or []:
if claim_ref not in claim_ids:
errors.append(f"evidence.{index}.claim_ref_unknown")
delegations = bundle.get("delegations")
if delegations is not None:
errors.extend(_validate_delegations(delegations, actor_ids))
if run.get("mode") == "observe" and isinstance(run.get("outcome"), Mapping):
if run["outcome"].get("certification_result") == "certified":
errors.append("observed_only_cannot_be_certified")
return errors
def _validate_delegations(delegations: object, actor_ids: set[object]) -> list[str]:
if not isinstance(delegations, list):
return ["delegations_invalid"]
errors: list[str] = []
records = {
item.get("delegation_id"): item
for item in delegations if isinstance(item, Mapping) and item.get("delegation_id")
}
for index, item in enumerate(delegations):
if not isinstance(item, Mapping):
errors.append(f"delegations.{index}_invalid")
continue
for actor_key in ("delegator_ref", "delegate_ref"):
if item.get(actor_key) not in actor_ids:
errors.append(f"delegations.{index}.{actor_key}_unknown")
current: Mapping[str, Any] = item
visited: set[object] = set()
depth = 0
while current.get("parent_delegation_ref"):
parent_ref = current.get("parent_delegation_ref")
if parent_ref in visited:
errors.append(f"delegations.{index}.cycle")
break
visited.add(parent_ref)
parent = records.get(parent_ref)
if not isinstance(parent, Mapping):
errors.append(f"delegations.{index}.parent_unknown")
break
depth += 1
current = parent
maximum_depth = item.get("maximum_depth")
if isinstance(maximum_depth, int) and depth > maximum_depth:
errors.append(f"delegations.{index}.maximum_depth_exceeded")
return errors
+400
View File
@@ -0,0 +1,400 @@
"""Deterministic security policy primitives shared by every CASAN adapter.
This module is deliberately stdlib-only. It never calls a model and never treats
model text as an enforcement decision. The JSON action registry is the canonical
machine-readable source; callers receive structured decisions rather than booleans.
"""
from __future__ import annotations
import json
import os
import re
import shutil
import stat
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping, MutableMapping, Optional
POLICY_VERSION = "1.0.0"
RISK_ORDER = {"low": 0, "medium": 1, "high": 2, "critical": 3}
PRODUCTION_PROFILES = {"prod", "production", "strict"}
DEVELOPMENT_PROFILES = {"", "dev", "development", "local", "test", "testing"}
FAILURE_POLICIES = {"halt", "quarantine", "require_approval", "record_only"}
def _harness_root() -> Path:
override = os.environ.get("CASAN_HARNESS_ROOT")
return Path(override).resolve() if override else Path(__file__).resolve().parents[1]
def _action_registry_path() -> Path:
override = os.environ.get("CASAN_ACTION_CLASS_REGISTRY")
return Path(override).resolve() if override else _harness_root() / "config" / "action-classes.json"
def _load_action_registry() -> dict[str, Any]:
with _action_registry_path().open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict) or not isinstance(payload.get("classes"), dict):
raise ValueError("action class registry is malformed")
return payload
def _normal(value: object) -> str:
return re.sub(r"[^a-z0-9]+", "_", str(value or "").strip().lower()).strip("_")
def _higher(left: str, right: str) -> str:
return right if RISK_ORDER.get(right, 3) > RISK_ORDER.get(left, 3) else left
def _content_risk(text: str) -> str:
value = (text or "").lower()
if re.search(r"\b(drop\s+table|truncate|password|api[_-]?key|secret|credential|shutdown|rm\s+-rf)\b", value):
return "high"
if re.search(r"\b(deploy|release|migration|permission|policy|external\s+api|database)\b", value):
return "medium"
return "low"
def _resource_risk(resource: str) -> str:
value = (resource or "").lower()
if re.search(r"(^|/)(\.env|\.ssh|\.aws)(/|$)|\.(pem|key|p12|pfx)$|credential|secret", value):
return "high"
if re.search(r"\b(prod|production|customer|tenant)\b", value):
return "medium"
return "low"
def _environment_risk(environment: str) -> str:
return "medium" if _normal(environment) in PRODUCTION_PROFILES else "low"
def classify_action(
action: str = "",
tool: str = "",
resource: str = "",
command: str = "",
) -> dict[str, Any]:
"""Classify an action using deterministic aliases and command patterns.
When several signals match, the class with the highest risk floor wins. An
unknown side-effect-shaped tool is therefore never downgraded by benign text.
"""
registry = _load_action_registry()
classes = registry["classes"]
candidates: list[tuple[str, str]] = []
action_key = _normal(action)
tool_key = _normal(tool)
action_class = registry.get("action_aliases", {}).get(action_key)
if action_class:
candidates.append(("action", action_class))
tool_class = registry.get("tool_aliases", {}).get(tool_key)
if tool_class:
candidates.append(("tool", tool_class))
combined = " ".join(part for part in (command, resource) if part)
for rule in registry.get("command_patterns", []):
try:
if re.search(str(rule["pattern"]), combined, re.IGNORECASE):
candidates.append(("command_or_resource", str(rule["class"])))
except (KeyError, re.error, TypeError) as exc:
raise ValueError("invalid action class pattern") from exc
shell_like = tool_key in {"bash", "shell", "exec", "run", "run_command", "terminal"}
if not candidates and shell_like:
candidates.append(("unknown_shell_command", "unknown"))
if not candidates:
candidates.append(("unclassified", "unknown"))
selected_source, selected_class = candidates[0]
selected_risk = str(classes[selected_class]["risk_floor"])
for source, candidate in candidates[1:]:
risk = str(classes[candidate]["risk_floor"])
if RISK_ORDER.get(risk, 3) > RISK_ORDER.get(selected_risk, 3):
selected_source, selected_class, selected_risk = source, candidate, risk
metadata = classes[selected_class]
return {
"schema_version": registry.get("schema_version", POLICY_VERSION),
"action_class": selected_class,
"classification_source": selected_source,
"risk_floor": selected_risk,
"side_effect_level": metadata["side_effect_level"],
"side_effecting": metadata["side_effect_level"] != "none",
"requires_approval": bool(metadata["requires_approval"]),
"actor_required": bool(metadata["actor_required"]),
"evidence_required": bool(metadata["evidence_required"]),
"matched_classes": [candidate for _source, candidate in candidates],
}
def evaluate_risk(
action: str = "",
tool: str = "",
resource: str = "",
command: str = "",
content: str = "",
actor: str = "",
environment: str = "",
) -> dict[str, Any]:
classification = classify_action(action, tool, resource, command)
actor_present = bool(str(actor or "").strip())
identity_risk = "high" if classification["actor_required"] and not actor_present else "low"
factors = {
"content_risk": _content_risk(content),
"action_risk": classification["risk_floor"],
"resource_risk": _resource_risk(resource),
"identity_risk": identity_risk,
"environment_risk": _environment_risk(environment),
}
effective = "low"
for factor in factors.values():
effective = _higher(effective, factor)
reason_codes: list[str] = ["action_risk_floor_applied"]
if classification["actor_required"] and not actor_present:
decision = "deny"
reason_codes.append("actor_identity_required")
elif classification["requires_approval"] or effective in {"high", "critical"}:
decision = "require_approval"
reason_codes.append("explicit_approval_required")
else:
decision = "allow"
reason_codes.append("risk_within_auto_approval_policy")
return {
"schema_version": POLICY_VERSION,
"policy_id": "casan.action-risk-floor",
"policy_version": POLICY_VERSION,
"decision": decision,
"reason_codes": reason_codes,
"effective_risk": effective,
"risk_factors": factors,
"actor_identity_present": actor_present,
"evidence_requirement": "required" if classification["evidence_required"] else "standard",
**classification,
}
def evaluate_registry_configuration(
mode: str,
profile: str,
explicit_value: Optional[str],
) -> dict[str, Any]:
normalized_mode = _normal(mode)
normalized_profile = _normal(profile)
explicit = None if explicit_value is None else _normal(explicit_value) in {"1", "true", "yes", "on", "enabled"}
production = normalized_profile in PRODUCTION_PROFILES
if explicit is False and production:
return {
"enabled": True,
"valid": False,
"unsafe_bypass": False,
"severity": "critical",
"reason_code": "h2_registry_bypass_forbidden",
}
if explicit is False:
return {
"enabled": False,
"valid": normalized_profile in DEVELOPMENT_PROFILES,
"unsafe_bypass": True,
"severity": "high",
"reason_code": "h2_registry_unsafe_development_bypass",
}
return {
"enabled": True if explicit is None else explicit,
"valid": True,
"unsafe_bypass": False,
"severity": "info",
"reason_code": "h2_registry_default_enabled" if explicit is None and normalized_mode == "enforce" else "h2_registry_enabled",
}
def evaluate_failure_policy(
*,
side_effecting: bool,
mode: str,
profile: str,
configured: Optional[str] = None,
) -> dict[str, Any]:
"""Select single-step failure handling without conflating telemetry success.
Invalid configuration fails closed. Production side effects may be made
stricter, but they may never be configured as record-only.
"""
normalized = _normal(configured or "")
production = _normal(profile) in PRODUCTION_PROFILES
enforce = _normal(mode) == "enforce"
if normalized and normalized not in FAILURE_POLICIES:
policy, source, valid = "halt", "invalid_policy_fail_closed", False
elif normalized == "record_only" and side_effecting and production:
policy, source, valid = "halt", "production_record_only_forbidden", False
elif normalized:
policy, source, valid = normalized, "configured", True
elif side_effecting and (enforce or production):
policy, source, valid = "halt", "safe_enforce_default", True
else:
policy, source, valid = "record_only", "observe_or_read_only_default", True
return {
"schema_version": POLICY_VERSION,
"policy_id": "casan.single-step-failure",
"policy_version": POLICY_VERSION,
"failure_policy": policy,
"source": source,
"valid": valid,
"side_effecting": side_effecting,
"mode": _normal(mode) or "observe",
"profile": _normal(profile) or "development",
"execution_result": "failed",
"telemetry_result": "recorded",
"assurance_may_continue": policy == "record_only",
"reason_codes": [source],
}
@dataclass(frozen=True)
class H2GateContext:
mode: str
actor: str
action: str
tool: str
execution_id: str
enforcement_path: str
idempotency_key: str
timeout_seconds: float = 8.0
def _h2_result(context: H2GateContext, underlying: str, reason_code: str, detail: str) -> dict[str, Any]:
enforce = _normal(context.mode) == "enforce"
actual_deny = underlying == "deny"
dependency_failure = underlying == "error"
execution_allowed = not enforce or (not actual_deny and not dependency_failure)
if enforce:
decision = "allow" if execution_allowed else "deny"
else:
decision = "observe_only"
return {
"schema_version": POLICY_VERSION,
"policy_id": "casan.h2.tool-registry",
"policy_version": POLICY_VERSION,
"decision": decision,
"underlying_decision": underlying,
"reason_codes": [reason_code],
"reason_code": reason_code,
"mode": _normal(context.mode) or "observe",
"actor": context.actor or "unidentified",
"action": context.action,
"tool": context.tool,
"execution_id": context.execution_id,
"enforcement_path": context.enforcement_path,
"execution_allowed": execution_allowed,
"certifiable": enforce and execution_allowed,
"assurance_status": "enforced" if enforce and execution_allowed else "denied" if enforce else "degraded",
"severity": "high" if reason_code != "h2_ok" else "info",
"detail": detail[:400],
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
}
def evaluate_h2_gate(
gate_path: str,
context: H2GateContext,
env: Optional[Mapping[str, str]] = None,
bash_path: str = "bash",
) -> dict[str, Any]:
"""Execute the H2 dependency and convert every outcome into one policy decision."""
path = Path(gate_path)
if not path.is_file():
return _h2_result(context, "error", "h2_gate_unavailable", "gate file is missing")
try:
mode_bits = stat.S_IMODE(path.stat().st_mode)
except OSError as exc:
return _h2_result(context, "error", "h2_gate_unavailable", f"gate stat failed: {type(exc).__name__}")
if mode_bits & 0o444 == 0:
return _h2_result(context, "error", "h2_gate_permission_denied", "gate file is not readable")
resolved_bash = shutil.which(bash_path) if not os.path.isabs(bash_path) else bash_path
if not resolved_bash or not Path(resolved_bash).is_file():
return _h2_result(context, "error", "h2_gate_unavailable", "bash runtime is unavailable")
child_env: MutableMapping[str, str] = dict(os.environ)
if env:
child_env.update({str(key): str(value) for key, value in env.items()})
child_env["CASAN_IDEMPOTENCY_KEY"] = context.idempotency_key
if context.actor and not child_env.get("CASAN_AGENT"):
child_env["CASAN_AGENT"] = context.actor
try:
proc = subprocess.run(
[resolved_bash, str(path), context.action],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=max(0.001, context.timeout_seconds),
env=child_env,
check=False,
text=True,
)
except subprocess.TimeoutExpired:
return _h2_result(context, "error", "h2_gate_timeout", "gate exceeded internal timeout")
except (OSError, ValueError, subprocess.SubprocessError) as exc:
return _h2_result(context, "error", "h2_gate_internal_error", type(exc).__name__)
stdout = proc.stdout.strip()[:4096]
stderr = proc.stderr.strip()[:4096]
if proc.returncode == 0 and re.search(r"\bTOOL_APPROVED\b", stdout):
return _h2_result(context, "allow", "h2_ok", stdout)
if re.search(r"\bTOOL_DENIED\b", stdout + "\n" + stderr):
reason_match = re.search(r"reason=([^\s]+)", stdout + "\n" + stderr)
reason = "h2_denied:%s" % (reason_match.group(1) if reason_match else "policy")
return _h2_result(context, "deny", reason, stderr or stdout)
if proc.returncode in {126, 127}:
return _h2_result(context, "error", "h2_gate_unavailable", stderr or "gate runtime unavailable")
if proc.returncode != 0:
return _h2_result(context, "error", "h2_gate_internal_error", stderr or stdout or f"exit={proc.returncode}")
return _h2_result(context, "error", "h2_gate_malformed_response", stdout or "empty response")
def evaluate_trust_capabilities(env: Optional[Mapping[str, str]] = None) -> dict[str, Any]:
values = dict(os.environ if env is None else env)
profile = _normal(values.get("CASAN_PROFILE", ""))
production = profile in PRODUCTION_PROFILES
signing_provider = _normal(values.get("CASAN_SIGNING_PROVIDER", "local_openssl"))
anchor_provider = _normal(values.get("CASAN_IMMUTABLE_ANCHOR_PROVIDER", "local_hash_chain"))
emergency = _normal(values.get("CASAN_TRUST_EMERGENCY_OVERRIDE", "")) in {"1", "true", "yes", "on"}
signing_external = signing_provider in {"vault", "vault_kms", "kms", "hsm"}
anchor_external = anchor_provider in {"s3_object_lock", "qldb", "external_worm"}
vault_addr = str(values.get("VAULT_ADDR") or "")
signing_configured = signing_external and vault_addr.startswith("https://") and bool(values.get("VAULT_TOKEN"))
anchor_configured = anchor_external and bool(values.get("CASAN_S3_BUCKET")) and bool(values.get("CASAN_S3_KMS_KEY_ID"))
ready = (not production) or (signing_configured and anchor_configured)
certifiable = ready and not emergency
reasons: list[str] = []
if production and not signing_configured:
reasons.append("external_signing_trust_root_required")
if production and not anchor_configured:
reasons.append("external_immutable_anchor_required")
if emergency:
reasons.append("emergency_local_trust_override_active")
return {
"schema_version": POLICY_VERSION,
"profile": profile or "development",
"production": production,
"signing_provider": signing_provider or "local_openssl",
"signing_capability": "external" if signing_configured else "local_or_unavailable",
"immutable_anchor_provider": anchor_provider or "local_hash_chain",
"immutable_anchor_capability": "external" if anchor_configured else "local_or_unavailable",
"ready": ready,
"certifiable": certifiable,
"emergency_override": emergency,
"severity": "critical" if emergency or not ready else "info",
"reason_codes": reasons or ["trust_capabilities_satisfied"],
}
@@ -0,0 +1,42 @@
"""Runtime supervision capability negotiation without pretending support."""
from __future__ import annotations
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from typing import Any
@dataclass(frozen=True)
class RuntimeCapabilities:
canCancel: bool = False
canPause: bool = False
canResume: bool = False
canRollback: bool = False
canReduceAuthority: bool = False
canQuarantine: bool = False
CAPABILITY_FOR_INTERVENTION = {
"cancel": "canCancel",
"pause": "canPause",
"resume": "canResume",
"roll_back": "canRollback",
"reduce_authority": "canReduceAuthority",
"quarantine": "canQuarantine",
}
def negotiate_intervention(intervention_id: str, intervention: str, requested_by: str, capabilities: RuntimeCapabilities) -> dict[str, Any]:
capability = CAPABILITY_FOR_INTERVENTION.get(intervention)
supported = bool(capability and getattr(capabilities, capability))
return {
"intervention_id": intervention_id,
"type": intervention,
"requested_by": requested_by,
"status": "pending" if supported else "unsupported",
"reason_code": "runtime_intervention_supported_pending_adapter" if supported else "runtime_intervention_unsupported",
"required_capability": capability,
"runtime_capabilities": asdict(capabilities),
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
}
+43
View File
@@ -0,0 +1,43 @@
"""Typed H1-H7 namespaces. Legacy `Hn` labels remain display aliases only."""
from __future__ import annotations
from enum import Enum
class AssuranceCategory(str, Enum):
RUNTIME_CONTROL = "runtime_control"
READINESS_CHECK = "readiness_check"
REPORT_DIMENSION = "report_dimension"
CERTIFICATION_CLAIM = "certification_claim"
class _HNamespace(str, Enum):
@property
def legacy_id(self) -> str:
return self.value.rsplit(".", 1)[-1]
class RuntimeControl(_HNamespace):
H1 = "RuntimeControl.H1"; H2 = "RuntimeControl.H2"; H3 = "RuntimeControl.H3"
H4 = "RuntimeControl.H4"; H5 = "RuntimeControl.H5"; H6 = "RuntimeControl.H6"; H7 = "RuntimeControl.H7"
class ReadinessCheck(_HNamespace):
H1 = "ReadinessCheck.H1"; H2 = "ReadinessCheck.H2"; H3 = "ReadinessCheck.H3"
H4 = "ReadinessCheck.H4"; H5 = "ReadinessCheck.H5"; H6 = "ReadinessCheck.H6"; H7 = "ReadinessCheck.H7"
class ReportDimension(_HNamespace):
H1 = "ReportDimension.H1"; H2 = "ReportDimension.H2"; H3 = "ReportDimension.H3"
H4 = "ReportDimension.H4"; H5 = "ReportDimension.H5"; H6 = "ReportDimension.H6"; H7 = "ReportDimension.H7"
class CertificationClaim(_HNamespace):
H1 = "CertificationClaim.H1"; H2 = "CertificationClaim.H2"; H3 = "CertificationClaim.H3"
H4 = "CertificationClaim.H4"; H5 = "CertificationClaim.H5"; H6 = "CertificationClaim.H6"; H7 = "CertificationClaim.H7"
def same_legacy_label(left: _HNamespace, right: _HNamespace) -> bool:
"""Compatibility display helper; never authorizes cross-category interpretation."""
return left.legacy_id == right.legacy_id
@@ -58,6 +58,10 @@
"type": ["string", "null"],
"description": "Client-native session id. Hashed by the bridge, never stored raw."
},
"actor": {
"type": ["string", "null"],
"description": "Stable actor identity asserted by the authenticated runtime boundary."
},
"integration_mode": {
"type": ["string", "null"],
"enum": ["casan_owned", "managed_hook", "project_hook", "observed_only", null],
@@ -167,7 +171,7 @@
"schema_version": { "type": "string" },
"decision": {
"type": "string",
"enum": ["allow", "block", "deny", "recorded", "certified", "non_certified", "error"]
"enum": ["allow", "block", "deny", "require_approval", "halt", "quarantine", "recorded", "certified", "non_certified", "error"]
},
"admission_id": { "type": ["string", "null"] },
"trace_id": { "type": ["string", "null"] },
@@ -0,0 +1,153 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://casan.dev/schemas/assurance-kernel/1.0.0",
"title": "CASAN Assurance Kernel Execution Envelope",
"description": "Framework-independent canonical wire contract. Runtime-specific fields belong under a namespaced extensions object.",
"type": "object",
"required": ["schema_version", "extension_namespace", "run", "actors", "steps", "evidence", "trace_links", "extensions"],
"properties": {
"schema_version": {"const": "1.0.0"},
"extension_namespace": {"type": "string", "pattern": "^[a-z][a-z0-9.-]+$"},
"run": {"$ref": "#/$defs/ExecutionRun"},
"actors": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/Actor"}},
"steps": {"type": "array", "items": {"$ref": "#/$defs/ExecutionStep"}},
"delegations": {"type": "array", "items": {"$ref": "#/$defs/Delegation"}},
"context_items": {"type": "array", "items": {"$ref": "#/$defs/ContextItem"}},
"runtime_constraints": {"type": "array", "items": {"$ref": "#/$defs/RuntimeConstraint"}},
"runtime_capabilities": {"$ref": "#/$defs/RuntimeCapabilities"},
"approvals": {"type": "array", "items": {"$ref": "#/$defs/Approval"}},
"interventions": {"type": "array", "items": {"$ref": "#/$defs/Intervention"}},
"claims": {"type": "array", "items": {"$ref": "#/$defs/Claim"}},
"evidence": {"type": "array", "items": {"$ref": "#/$defs/EvidenceItem"}},
"trace_links": {"type": "array", "items": {"$ref": "#/$defs/TraceLink"}},
"extensions": {"type": "object", "additionalProperties": {"type": "object"}}
},
"additionalProperties": false,
"$defs": {
"Timestamp": {"type": "string", "format": "date-time"},
"Outcome": {
"type": "object",
"required": ["execution_result", "assurance_result", "certification_result", "business_result"],
"properties": {
"execution_result": {"enum": ["pending", "success", "failed", "cancelled", "quarantined", "unknown"]},
"assurance_result": {"enum": ["pending", "passed", "failed", "degraded", "not_evaluated", "unknown"]},
"certification_result": {"enum": ["pending", "certified", "non_certified", "ineligible"]},
"business_result": {"enum": ["achieved", "not_achieved", "partial", "not_evaluated", "unknown"]}
},
"additionalProperties": false
},
"ExecutionRun": {
"type": "object",
"required": ["run_id", "tenant", "project", "environment", "runtime", "mode", "requested_goal", "started_at", "status", "risk_summary", "correlation_id", "outcome"],
"properties": {
"run_id": {"type": "string", "minLength": 1},
"parent_run_id": {"type": ["string", "null"]},
"tenant": {"type": "string"},
"project": {"type": "string"},
"environment": {"type": "string"},
"runtime": {"type": "object", "required": ["type", "version"], "properties": {"type": {"type": "string"}, "version": {"type": "string"}}, "additionalProperties": false},
"mode": {"enum": ["observe", "enforce"]},
"requested_goal": {"type": "string"},
"started_at": {"$ref": "#/$defs/Timestamp"},
"completed_at": {"anyOf": [{"$ref": "#/$defs/Timestamp"}, {"type": "null"}]},
"status": {"enum": ["pending", "running", "success", "failed", "cancelled", "quarantined", "unknown"]},
"risk_summary": {"type": "object"},
"evidence_manifest_ref": {"type": ["string", "null"]},
"correlation_id": {"type": "string", "minLength": 1},
"outcome": {"$ref": "#/$defs/Outcome"}
},
"additionalProperties": false
},
"ExecutionStep": {
"type": "object",
"required": ["step_id", "run_id", "sequence", "actor_ref", "action", "input_context_refs", "policy_decisions", "verification_results", "evidence_refs", "outcome", "started_at", "completed_at"],
"properties": {
"step_id": {"type": "string"}, "run_id": {"type": "string"}, "parent_step_id": {"type": ["string", "null"]}, "sequence": {"type": "integer", "minimum": 1},
"actor_ref": {"type": "string"}, "action": {"$ref": "#/$defs/Action"}, "resource": {"type": ["object", "string", "null"]},
"input_context_refs": {"type": "array", "items": {"type": "string"}}, "policy_decisions": {"type": "array", "items": {"$ref": "#/$defs/PolicyDecision"}},
"tool_invocation": {"type": ["object", "null"]}, "verification_results": {"type": "array", "items": {"$ref": "#/$defs/VerificationResult"}},
"evidence_refs": {"type": "array", "items": {"type": "string"}}, "outcome": {"$ref": "#/$defs/Outcome"},
"started_at": {"$ref": "#/$defs/Timestamp"}, "completed_at": {"anyOf": [{"$ref": "#/$defs/Timestamp"}, {"type": "null"}]}
},
"additionalProperties": false
},
"Actor": {
"type": "object",
"required": ["actor_id", "actor_type", "issuer", "trust_level"],
"properties": {
"actor_id": {"type": "string"}, "actor_type": {"enum": ["human", "agent", "child_agent", "model", "tool", "service_account", "runtime", "approver", "policy_engine"]},
"issuer": {"type": "string"}, "trust_level": {"enum": ["unverified", "asserted", "verified", "hardware_backed"]}, "authentication_evidence_ref": {"type": ["string", "null"]}
},
"additionalProperties": false
},
"Delegation": {
"type": "object",
"required": ["delegation_id", "delegator_ref", "delegate_ref", "allowed_authority", "resource_scope", "expiry", "maximum_depth", "revoked"],
"properties": {
"delegation_id": {"type": "string"}, "delegator_ref": {"type": "string"}, "delegate_ref": {"type": "string"}, "allowed_authority": {"type": "array", "items": {"type": "string"}},
"resource_scope": {"type": "array", "items": {"type": "string"}}, "expiry": {"$ref": "#/$defs/Timestamp"}, "maximum_depth": {"type": "integer", "minimum": 0},
"parent_delegation_ref": {"type": ["string", "null"]}, "revoked": {"type": "boolean"}, "approval_ref": {"type": ["string", "null"]}
},
"additionalProperties": false
},
"ContextItem": {
"type": "object", "required": ["context_id", "source", "trust_classification", "content_hash", "transform_history", "compression_lineage", "classification", "injection_scan_result"],
"properties": {
"context_id": {"type": "string"}, "source": {"type": "string"}, "trust_classification": {"enum": ["trusted", "untrusted", "mixed", "unknown"]}, "content_hash": {"type": "string"},
"transform_history": {"type": "array", "items": {"type": "object"}}, "compression_lineage": {"type": "array", "items": {"type": "string"}},
"origin_ref": {"type": ["string", "null"]}, "classification": {"enum": ["instruction", "data", "mixed", "unknown"]}, "injection_scan_result": {"type": "object"}
}, "additionalProperties": false
},
"Action": {
"type": "object", "required": ["class", "name", "side_effect_level"],
"properties": {"class": {"enum": ["read_only", "write", "delete", "database_mutation", "migration", "deployment", "release", "credential_access", "identity_permission_modification", "external_network_side_effect", "infrastructure_modification", "unknown"]}, "name": {"type": "string"}, "tool": {"type": ["string", "null"]}, "side_effect_level": {"enum": ["none", "sensitive_read", "write", "destructive", "external", "unknown"]}, "required_authority": {"type": ["string", "null"]}, "requested_operation": {"type": ["string", "null"]}, "environment": {"type": ["string", "null"]}},
"additionalProperties": false
},
"ResourceAccess": {
"type": "object", "required": ["resource", "operation", "side_effect_level", "environment"],
"properties": {"resource": {"type": "string"}, "operation": {"type": "string"}, "side_effect_level": {"type": "string"}, "required_authority": {"type": ["string", "null"]}, "environment": {"type": "string"}}, "additionalProperties": false
},
"PolicyDecision": {
"type": "object", "required": ["policy_id", "policy_version", "decision", "reason_codes", "effective_risk", "input_facts", "enforcement_point", "timestamp", "decision_engine_identity"],
"properties": {"policy_id": {"type": "string"}, "policy_version": {"type": "string"}, "decision": {"enum": ["allow", "deny", "quarantine", "require_approval", "transform", "observe_only"]}, "reason_codes": {"type": "array", "items": {"type": "string"}}, "effective_risk": {"enum": ["low", "medium", "high", "critical"]}, "input_facts": {"type": "object"}, "enforcement_point": {"type": "string"}, "timestamp": {"$ref": "#/$defs/Timestamp"}, "decision_engine_identity": {"type": "string"}, "evidence_ref": {"type": ["string", "null"]}}, "additionalProperties": false
},
"RuntimeConstraint": {
"type": "object", "required": ["constraint_id", "kind", "limit", "enforcement"],
"properties": {"constraint_id": {"type": "string"}, "kind": {"enum": ["iteration", "time", "cost", "token", "child_agent_count", "graph_depth", "retry", "repetition", "network", "filesystem", "tool", "environment"]}, "limit": {}, "enforcement": {"enum": ["hard", "soft", "observe"]}}, "additionalProperties": false
},
"RuntimeCapabilities": {
"type": "object",
"required": ["canCancel", "canPause", "canResume", "canRollback", "canReduceAuthority", "canQuarantine"],
"properties": {"canCancel": {"type": "boolean"}, "canPause": {"type": "boolean"}, "canResume": {"type": "boolean"}, "canRollback": {"type": "boolean"}, "canReduceAuthority": {"type": "boolean"}, "canQuarantine": {"type": "boolean"}},
"additionalProperties": false
},
"VerificationRequirement": {
"type": "object", "required": ["requirement_id", "validator", "expected_result", "independence_level", "failure_severity"],
"properties": {"requirement_id": {"type": "string"}, "validator": {"type": "string"}, "expected_result": {}, "independence_level": {"enum": ["same_runtime", "independent_process", "independent_service", "external_authority"]}, "failure_severity": {"enum": ["info", "warning", "error", "critical"]}}, "additionalProperties": false
},
"VerificationResult": {
"type": "object", "required": ["requirement_id", "validator", "expected_result", "actual_result", "status", "evidence_refs", "independence_level", "failure_severity"],
"properties": {"requirement_id": {"type": "string"}, "validator": {"type": "string"}, "expected_result": {}, "actual_result": {}, "status": {"enum": ["passed", "failed", "degraded", "not_run"]}, "evidence_refs": {"type": "array", "items": {"type": "string"}}, "independence_level": {"type": "string"}, "failure_severity": {"type": "string"}}, "additionalProperties": false
},
"Claim": {
"type": "object", "required": ["claim_id", "statement", "evidence_refs", "validation_status"],
"properties": {"claim_id": {"type": "string"}, "statement": {"type": "string"}, "evidence_refs": {"type": "array", "items": {"type": "string"}}, "validation_status": {"enum": ["validated", "rejected", "insufficient", "not_validated"]}}, "additionalProperties": false
},
"EvidenceItem": {
"type": "object", "required": ["evidence_id", "claim_refs", "integrity", "producer_identity", "timestamp", "artifact_ref", "validation_status", "retention_class"],
"properties": {"evidence_id": {"type": "string"}, "claim_refs": {"type": "array", "items": {"type": "string"}}, "integrity": {"type": "object", "required": ["algorithm", "digest"], "properties": {"algorithm": {"const": "sha256"}, "digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"}, "source_content_hash": {"type": ["string", "null"]}}, "additionalProperties": false}, "producer_identity": {"type": "string"}, "timestamp": {"$ref": "#/$defs/Timestamp"}, "artifact_ref": {"type": "string"}, "validation_status": {"enum": ["valid", "invalid", "unverified", "unavailable"]}, "retention_class": {"type": "string"}, "category": {"enum": ["runtime_control", "readiness_check", "report_dimension", "certification_claim"]}}, "additionalProperties": false
},
"TraceLink": {
"type": "object", "required": ["type", "from", "to"],
"properties": {"type": {"enum": ["requirement_to_policy", "policy_to_decision", "decision_to_action", "action_to_artifact", "artifact_to_verification", "verification_to_evidence", "evidence_to_outcome", "parent_to_child_execution"]}, "from": {"type": "string"}, "to": {"type": "string"}}, "additionalProperties": false
},
"Approval": {
"type": "object", "required": ["approval_id", "decision", "approver_ref", "timestamp"],
"properties": {"approval_id": {"type": "string"}, "decision": {"enum": ["approve", "reject"]}, "approver_ref": {"type": "string"}, "reason": {"type": ["string", "null"]}, "timestamp": {"$ref": "#/$defs/Timestamp"}}, "additionalProperties": false
},
"Intervention": {
"type": "object", "required": ["intervention_id", "type", "requested_by", "status", "timestamp"],
"properties": {"intervention_id": {"type": "string"}, "type": {"enum": ["pause", "resume", "cancel", "quarantine", "reduce_authority", "redirect", "roll_back"]}, "requested_by": {"type": "string"}, "status": {"enum": ["applied", "rejected", "unsupported", "pending"]}, "timestamp": {"$ref": "#/$defs/Timestamp"}}, "additionalProperties": false
}
}
}
@@ -110,6 +110,10 @@ LATENCY_MS=$((END_MS - START_MS))
if [[ ! -f "$OUTPUT_FILE" ]]; then
STATUS="failed"
ERROR_MSG="${ERROR_MSG:-output file not produced}"
# A zero command exit does not make the step successful when the runtime
# contract requires an output artifact and none was produced. Telemetry can
# record this failure successfully, but must propagate a failed step outcome.
[[ "$EXIT_CODE" -eq 0 ]] && EXIT_CODE=1
: > "$OUTPUT_FILE"
fi
@@ -24,6 +24,7 @@ fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/casan-paths.sh"
PROJECT_ROOT="$CASAN_APP_ROOT"
KERNEL_CLI="$CASAN_HARNESS_ROOT/scripts/python/kernel_cli.py"
# SEC-23 (MT-01): make state (control-plane settings, telemetry, audit) tenant-scoped
# when CASAN_TENANT_ID is set, so a run for tenant A never touches tenant B's state.
# No-op when no tenant is set (baseline unchanged); invalid tenant fails closed.
@@ -56,12 +57,52 @@ write_phase_report() {
"$ACTION_NAME" "$CACHE_STATUS" "$PHASE_LOG" > "$PHASE_REPORT" 2>/dev/null || true
}
emit_failed_kernel() { # phase rc — best effort, never masks the original failure
local failed_phase="$1" failed_rc="$2"
[[ -f "$KERNEL_CLI" ]] || return 0
local run_id="${EXECUTION_ID:-native-failed-${TRACE_SUFFIX:-$$}}"
local mode="${NATIVE_MODE:-observe}"
local event bundle path
event="$(CASAN_NATIVE_RISK="${ACTION_RISK_JSON:-}" python3 - "$run_id" "$ACTION_NAME" "$mode" "$failed_phase" "$failed_rc" "${CASAN_ACTOR:-${CASAN_AGENT:-}}" <<'PY'
import json, os, sys
run_id, action, mode, phase, rc, actor = sys.argv[1:]
try: risk = json.loads(os.environ.get("CASAN_NATIVE_RISK") or "{}")
except ValueError: risk = {}
print(json.dumps({
"run_id": run_id, "correlation_id": run_id, "action": action,
"tool": action, "actor": actor, "mode": mode,
"environment": os.environ.get("CASAN_PROFILE", "development"),
"execution_status": "failed", "assurance_status": "failed",
"certification_status": "non_certified", "policy_decisions": [risk] if risk else [],
"extensions": {"failed_phase": phase, "exit_code": int(rc)},
}, separators=(",", ":")))
PY
)" || return 0
bundle="$(printf '%s' "$event" | python3 "$KERNEL_CLI" adapt-native - 2>/dev/null)" || return 0
path="$CASAN_STATE_ROOT/logs/kernel/$run_id.json"
CASAN_KERNEL_BUNDLE="$bundle" python3 - "$path" <<'PY' || return 0
import json, os, sys, tempfile
path = sys.argv[1]; payload = json.loads(os.environ["CASAN_KERNEL_BUNDLE"])
os.makedirs(os.path.dirname(path), exist_ok=True)
fd, tmp = tempfile.mkstemp(prefix=".kernel-", dir=os.path.dirname(path))
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(payload, handle, sort_keys=True, separators=(",", ":")); handle.write("\n")
handle.flush(); os.fsync(handle.fileno())
os.replace(tmp, path)
finally:
try: os.unlink(tmp)
except OSError: pass
PY
}
run_phase() { # <phase-name> <command...> — preserves the failing rc exactly
local phase="$1"; shift
local rc=0
"$@" || rc=$?
record_phase "$phase" "$rc"
if [[ "$rc" -ne 0 ]]; then
emit_failed_kernel "$phase" "$rc"
write_phase_report
exit "$rc"
fi
@@ -76,9 +117,16 @@ hash_text() {
}
CMD_STR="${*:-no_cmd}"
NATIVE_MODE="${CASAN_ENFORCEMENT_MODE:-}"
if [[ -z "$NATIVE_MODE" ]]; then
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ]]; then NATIVE_MODE="enforce"; else NATIVE_MODE="observe"; fi
fi
case "$NATIVE_MODE" in observe|enforce) : ;; *) NATIVE_MODE="enforce"; casan_log error harness "INVALID_ENFORCEMENT_MODE fail_closed=enforce" ;; esac
INPUT_HASH="$(cat "$INPUT_FILE" | hash_text)"
CMD_HASH="$(printf '%s' "$CMD_STR" | hash_text)"
IDEMPOTENCY_KEY="$(printf '%s|%s|%s' "$INPUT_HASH" "$CMD_HASH" "$ACTION_NAME" | hash_text)"
EXECUTION_ID="${CASAN_EXECUTION_ID:-native-${IDEMPOTENCY_KEY:0:24}}"
export CASAN_EXECUTION_ID="$EXECUTION_ID"
CACHE_META="$CACHE_DIR/$IDEMPOTENCY_KEY.json"
CACHE_OUT="$CACHE_DIR/$IDEMPOTENCY_KEY.output"
@@ -92,7 +140,7 @@ casan_log debug harness "action=$ACTION_NAME input=$INPUT_FILE output=$FINAL_OUT
# C7: honor an engaged kill-switch before doing any work (incident containment).
# Opt-in (default off) so the baseline is unchanged. SEC-17 (ARCH-03): under
# CASAN_PROFILE=prod it defaults ON (secure-by-default); an explicit =0 still wins.
if [[ "${CASAN_KILLSWITCH_ENFORCE:-0}" == "1" || ( -z "${CASAN_KILLSWITCH_ENFORCE+x}" && "${CASAN_PROFILE:-}" == "prod" ) ]]; then
if [[ "${CASAN_KILLSWITCH_ENFORCE:-0}" == "1" || ( -z "${CASAN_KILLSWITCH_ENFORCE+x}" && ( "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ) ) ]]; then
KS_SCOPE="${CASAN_KILLSWITCH_SCOPE:-project}"
KS_ID="${CASAN_KILLSWITCH_ID:-${CASAN_PROJECT:-current}}"
if ! bash "$SCRIPT_DIR/kill-switch.sh" check "$KS_SCOPE" "$KS_ID" >/dev/null 2>&1; then
@@ -116,7 +164,7 @@ fi
# signed manifest and REFUSE to run on any drift — editing a gate/policy is a bypass
# that leaves no input trace. Only active when a manifest is provisioned (so dev and
# prod-without-a-manifest are unaffected); a present-but-drifted bundle fails closed.
if [[ ( "${CASAN_PROFILE:-}" == "prod" || "${CASAN_VERIFY_STRICT:-}" == "1" ) \
if [[ ( "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" || "${CASAN_VERIFY_STRICT:-}" == "1" ) \
&& -f "$SCRIPT_DIR/bundle-integrity.py" ]]; then
BUNDLE_MANIFEST="${CASAN_BUNDLE_MANIFEST:-$CASAN_GOVERNANCE_ROOT/harness-bundle-manifest.json}"
if [[ -f "$BUNDLE_MANIFEST" ]]; then
@@ -132,15 +180,62 @@ fi
run_phase "H4-in" "$SCRIPT_DIR/security-check.sh" "$INPUT_FILE" "$SAFE_INPUT" input
run_phase "H5" "$SCRIPT_DIR/governance-check.sh" "$SAFE_INPUT" "$APPROVED_INPUT" "$ACTION_NAME"
# H2 tool registry gate is in the line of fire for side-effecting actions:
# it enforces idempotency key, per-agent permission, and rollback strategy
# before the command is allowed to execute. The wrapper already derived a
# content-addressed idempotency key above.
case "$ACTION_NAME" in
write_code|migration|deploy|db_write|external_api|write_file)
run_phase "H2-gate" env CASAN_IDEMPOTENCY_KEY="$IDEMPOTENCY_KEY" "$SCRIPT_DIR/tool-registry-gate.sh" "$ACTION_NAME"
;;
esac
# Canonical action classification decides whether the H2 registry and isolated
# executor are required. A classifier failure is treated as unknown/high-risk.
ACTION_RISK_JSON=""
ACTION_RISK_RC=0
ACTION_RISK_JSON="$(python3 "$KERNEL_CLI" risk --action "$ACTION_NAME" --tool "$ACTION_NAME" \
--command "$CMD_STR" --content-file "$SAFE_INPUT" --actor "${CASAN_ACTOR:-${CASAN_AGENT:-}}" \
--environment "${CASAN_PROFILE:-development}" 2>/dev/null)" || ACTION_RISK_RC=$?
if [[ "$ACTION_RISK_RC" -eq 0 && -n "$ACTION_RISK_JSON" ]]; then
ACTION_FIELDS="$(python3 - "$ACTION_RISK_JSON" <<'PY'
import json, sys
p=json.loads(sys.argv[1])
print("%s\t%s" % ("1" if p["side_effecting"] else "0", p["action_class"]))
PY
)" || ACTION_RISK_RC=$?
fi
if [[ "$ACTION_RISK_RC" -eq 0 && -n "${ACTION_FIELDS:-}" ]]; then
IFS=$'\t' read -r SIDE_EFFECTING ACTION_CLASS <<< "$ACTION_FIELDS"
else
SIDE_EFFECTING=1
ACTION_CLASS="unknown"
casan_log error harness "ACTION_CLASSIFIER_FAILED_CLOSED action=$ACTION_NAME"
fi
if [[ "$SIDE_EFFECTING" == "1" ]]; then
REGISTRY_ARGS=(registry-config --mode "$NATIVE_MODE" --profile "${CASAN_PROFILE:-development}" \
--evidence-log "$CASAN_STATE_ROOT/logs/policy/h2-registry-config.jsonl")
[[ -n "${CASAN_H2_REGISTRY+x}" ]] && REGISTRY_ARGS+=(--explicit "$CASAN_H2_REGISTRY")
REGISTRY_RC=0
REGISTRY_JSON="$(python3 "$KERNEL_CLI" "${REGISTRY_ARGS[@]}")" || REGISTRY_RC=$?
if [[ "$REGISTRY_RC" -ne 0 ]]; then
record_phase "H2-config" "$REGISTRY_RC"
echo "H2_REGISTRY_CONFIGURATION_DENIED $REGISTRY_JSON" >&2
write_phase_report
exit "$REGISTRY_RC"
fi
REGISTRY_ENABLED="$(python3 -c 'import json,sys; print("1" if json.load(sys.stdin)["enabled"] else "0")' <<< "$REGISTRY_JSON")"
if [[ "$REGISTRY_ENABLED" == "1" ]]; then
H2_ACTION="$ACTION_NAME"
case "$ACTION_CLASS" in
write) H2_ACTION="write_file" ;;
delete) H2_ACTION="delete_file" ;;
database_mutation) H2_ACTION="db_write" ;;
deployment|release|infrastructure_modification) H2_ACTION="deploy" ;;
external_network_side_effect) H2_ACTION="external_api" ;;
unknown) H2_ACTION="unknown_tool" ;;
esac
run_phase "H2-gate" python3 "$KERNEL_CLI" h2-gate \
--gate "${CASAN_H2_GATE_PATH:-$SCRIPT_DIR/tool-registry-gate.sh}" --mode "$NATIVE_MODE" \
--actor "${CASAN_ACTOR:-${CASAN_AGENT:-}}" --action "$H2_ACTION" --tool "$ACTION_NAME" \
--execution-id "$EXECUTION_ID" --enforcement-path "native_harness.pre_execution.h2_registry" \
--idempotency-key "$IDEMPOTENCY_KEY" --timeout "${CASAN_H2_GATE_TIMEOUT_SECONDS:-8}" \
--evidence-log "$CASAN_STATE_ROOT/logs/policy/h2-decisions.jsonl"
else
casan_log warn harness "HIGH H2 registry unsafe development/test bypass active; run is non-certifiable"
fi
fi
# T4: propagate step name so any nested model calls (model-call.py) log against the same step
# name, enabling provider-cost-lookup.py to match real Ollama token counts in agent-metrics.sh.
@@ -155,13 +250,34 @@ if [[ -f "$CACHE_META" && -f "$CACHE_OUT" ]]; then
run_phase "H6-exec" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- bash -c 'cp "$1" "$CASAN_OUTPUT"' _ "$CACHE_OUT"
elif [[ "$#" -gt 0 ]]; then
CACHE_STATUS="stored"
run_phase "H6-exec" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- \
"$SCRIPT_DIR/tool-exec.sh" "$TOOL_TIMEOUT" -- "$@"
if [[ "$SIDE_EFFECTING" == "1" && "$NATIVE_MODE" == "enforce" ]]; then
run_phase "H6-exec" env CASAN_ENFORCEMENT_MODE="$NATIVE_MODE" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- \
"$SCRIPT_DIR/sandbox-run.sh" --workspace "$PROJECT_ROOT" --timeout "$TOOL_TIMEOUT" -- "$@"
else
run_phase "H6-exec" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- \
"$SCRIPT_DIR/tool-exec.sh" "$TOOL_TIMEOUT" -- "$@"
fi
else
CACHE_STATUS="stored"
run_phase "H6-exec" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT"
fi
TOOL_OUTPUT_MAX_BYTES="${CASAN_TOOL_OUTPUT_MAX_BYTES:-1048576}"
if [[ ! "$TOOL_OUTPUT_MAX_BYTES" =~ ^[1-9][0-9]*$ ]]; then
casan_log error harness "TOOL_OUTPUT_LIMIT_INVALID value=$TOOL_OUTPUT_MAX_BYTES"
: > "$FINAL_OUTPUT"
exit 2
fi
RAW_OUTPUT_BYTES="$(wc -c < "$RAW_OUTPUT" | tr -d ' ')"
if [[ "$RAW_OUTPUT_BYTES" -gt "$TOOL_OUTPUT_MAX_BYTES" ]]; then
record_phase "H4-output-size" 2
casan_log error harness "TOOL_OUTPUT_QUARANTINED bytes=$RAW_OUTPUT_BYTES limit=$TOOL_OUTPUT_MAX_BYTES"
: > "$FINAL_OUTPUT"
write_phase_report
echo "TOOL_OUTPUT_QUARANTINED reason=output_size_limit bytes=$RAW_OUTPUT_BYTES limit=$TOOL_OUTPUT_MAX_BYTES" >&2
exit 2
fi
# V7: tool output can carry indirect injection that would re-enter a downstream
# model's context. Scan RAW_OUTPUT for injection/secret patterns before it is
# reused. Mode: off | warn (default) | block. Strict mode upgrades to block.
@@ -170,7 +286,7 @@ fi
TOOL_OUTPUT_SCAN_MODE="${CASAN_TOOL_OUTPUT_SCAN:-}"
if [[ -z "$TOOL_OUTPUT_SCAN_MODE" ]]; then
# SEC-17/M-02: prod profile defaults tool-output scanning to block (fail-closed).
if [[ "${CASAN_SECURITY_STRICT:-0}" == "1" || ( -z "${CASAN_SECURITY_STRICT+x}" && "${CASAN_PROFILE:-}" == "prod" ) ]]; then TOOL_OUTPUT_SCAN_MODE="block"; else TOOL_OUTPUT_SCAN_MODE="warn"; fi
if [[ "${CASAN_SECURITY_STRICT:-0}" == "1" || ( -z "${CASAN_SECURITY_STRICT+x}" && ( "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ) ) ]]; then TOOL_OUTPUT_SCAN_MODE="block"; else TOOL_OUTPUT_SCAN_MODE="warn"; fi
fi
if [[ "$TOOL_OUTPUT_SCAN_MODE" != "off" ]]; then
TOS_RC=0
@@ -204,6 +320,65 @@ EOF
cp "$FINAL_OUTPUT" "$CACHE_OUT"
fi
# Dual-emit the framework-independent kernel envelope. Legacy phase reports and
# outputs remain unchanged; the canonical contract is an additive artifact.
TRUST_JSON="$(python3 "$KERNEL_CLI" trust-capabilities 2>/dev/null || true)"
RUN_CERTIFICATION="non_certified"
RUN_ASSURANCE="passed"
[[ "$NATIVE_MODE" == "observe" ]] && RUN_ASSURANCE="degraded"
if [[ "$NATIVE_MODE" == "enforce" && -n "$TRUST_JSON" ]]; then
TRUST_CERTIFIABLE="$(python3 -c 'import json,sys; print("1" if json.load(sys.stdin).get("certifiable") else "0")' <<< "$TRUST_JSON" 2>/dev/null || echo 0)"
REGISTRY_BYPASS="$(printf '%s' "${REGISTRY_JSON:-{}}" | python3 -c 'import json,sys; print("1" if json.load(sys.stdin).get("unsafe_bypass") else "0")' 2>/dev/null || echo 0)"
[[ "$REGISTRY_BYPASS" == "1" ]] && RUN_ASSURANCE="degraded"
[[ "$TRUST_CERTIFIABLE" == "1" && "$REGISTRY_BYPASS" == "0" ]] && RUN_CERTIFICATION="certified"
fi
NATIVE_EVENT="$(CASAN_NATIVE_RISK="$ACTION_RISK_JSON" python3 - "$EXECUTION_ID" "$ACTION_NAME" "$ACTION_CLASS" "$NATIVE_MODE" "$RUN_ASSURANCE" "$RUN_CERTIFICATION" "${CASAN_ACTOR:-${CASAN_AGENT:-}}" "$INPUT_HASH" "$CMD_HASH" <<'PY'
import json, os, sys
run_id, action, action_class, mode, assurance, certification, actor, input_hash, command_hash = sys.argv[1:]
try:
risk = json.loads(os.environ.get("CASAN_NATIVE_RISK") or "{}")
except ValueError:
risk = {}
print(json.dumps({
"run_id": run_id, "correlation_id": run_id, "action": action,
"tool": action, "actor": actor, "mode": mode,
"environment": os.environ.get("CASAN_PROFILE", "development"),
"execution_status": "success", "assurance_status": assurance,
"certification_status": certification,
"policy_decisions": [risk] if risk else [],
"extensions": {"input_hash": input_hash, "command_hash": command_hash, "action_class": action_class},
}, separators=(",", ":")))
PY
)"
KERNEL_RC=0
KERNEL_BUNDLE="$(printf '%s' "$NATIVE_EVENT" | python3 "$KERNEL_CLI" adapt-native -)" || KERNEL_RC=$?
if [[ "$KERNEL_RC" -ne 0 ]]; then
casan_log error harness "KERNEL_CONTRACT_EMISSION_FAILED rc=$KERNEL_RC"
if [[ "$NATIVE_MODE" == "enforce" ]]; then
: > "$FINAL_OUTPUT"
exit 2
fi
RUN_CERTIFICATION="non_certified"
else
KERNEL_PATH="$CASAN_STATE_ROOT/logs/kernel/$EXECUTION_ID.json"
python3 - "$KERNEL_PATH" "$KERNEL_BUNDLE" <<'PY'
import json, os, sys, tempfile
path, raw = sys.argv[1:]
os.makedirs(os.path.dirname(path), exist_ok=True)
payload = json.loads(raw)
fd, tmp = tempfile.mkstemp(prefix=".kernel-", dir=os.path.dirname(path))
try:
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(payload, handle, sort_keys=True, separators=(",", ":"))
handle.write("\n")
handle.flush(); os.fsync(handle.fileno())
os.replace(tmp, path)
finally:
try: os.unlink(tmp)
except OSError: pass
PY
fi
write_phase_report
casan_log debug harness "action=$ACTION_NAME complete cache=$CACHE_STATUS"
echo "CASAN_HARNESS_COMPLETE cache=$CACHE_STATUS key=$IDEMPOTENCY_KEY output=$FINAL_OUTPUT"
echo "CASAN_HARNESS_COMPLETE cache=$CACHE_STATUS key=$IDEMPOTENCY_KEY execution=success assurance=$RUN_ASSURANCE certification=$RUN_CERTIFICATION output=$FINAL_OUTPUT"
@@ -58,27 +58,48 @@ TRACE_ID="$(new_trace_id)"
TIMESTAMP="$(timestamp)"
INPUT="$(cat "$INPUT_FILE")"
LOWER_INPUT="$(printf '%s' "$INPUT" | tr '[:upper:]' '[:lower:]')"
ACTOR="${CASAN_ACTOR:-developer}"
ACTOR="${CASAN_ACTOR:-${CASAN_AGENT:-}}"
APPROVER="${CASAN_APPROVER:-}"
APPROVAL_DECISION="${CASAN_APPROVAL_DECISION:-auto}"
AUDIT_LOG="$AUDIT_DIR/audit.jsonl"
RISK_LEVEL="low"
REASONS=()
case "$ACTION_NAME" in
deploy|launch|write_code|write_file|migration|db_write|external_api|tool_call)
RISK_LEVEL="medium"
REASONS+=("sensitive-action:$ACTION_NAME")
;;
esac
if printf '%s' "$LOWER_INPUT" | grep -Eq "(delete|drop table|password|api[_-]?key|secret|token|credential|migration|deploy|external api|shutdown|dump database)"; then
ACTION_CLASS="unknown"
RISK_FACTORS_JSON='{"action_risk":"high","content_risk":"high","environment_risk":"low","identity_risk":"low","resource_risk":"low"}'
EVIDENCE_REQUIREMENT="required"
RISK_POLICY_DECISION="require_approval"
KERNEL_CLI="$CASAN_HARNESS_ROOT/scripts/python/kernel_cli.py"
RISK_JSON=""
RISK_RC=0
if [[ -f "$KERNEL_CLI" ]]; then
RISK_JSON="$(python3 "$KERNEL_CLI" risk --action "$ACTION_NAME" --tool "$ACTION_NAME" \
--content-file "$INPUT_FILE" --actor "$ACTOR" --environment "${CASAN_PROFILE:-development}" 2>/dev/null)" || RISK_RC=$?
else
RISK_RC=127
fi
if [[ "$RISK_RC" -eq 0 && -n "$RISK_JSON" ]]; then
RISK_FIELDS="$(python3 - "$RISK_JSON" <<'PY'
import json, sys
payload = json.loads(sys.argv[1])
print("\t".join([
str(payload["action_class"]),
str(payload["effective_risk"]),
json.dumps(payload["risk_factors"], sort_keys=True, separators=(",", ":")),
str(payload["evidence_requirement"]),
str(payload["decision"]),
]))
PY
)" || RISK_RC=$?
fi
if [[ "$RISK_RC" -eq 0 && -n "${RISK_FIELDS:-}" ]]; then
IFS=$'\t' read -r ACTION_CLASS RISK_LEVEL RISK_FACTORS_JSON EVIDENCE_REQUIREMENT RISK_POLICY_DECISION <<< "$RISK_FIELDS"
REASONS+=("action-risk-floor:$ACTION_CLASS")
else
RISK_LEVEL="high"
REASONS+=("high-risk-content")
elif printf '%s' "$LOWER_INPUT" | grep -Eq "(internal|config|system|policy|permission)"; then
[[ "$RISK_LEVEL" == "low" ]] && RISK_LEVEL="medium"
REASONS+=("medium-risk-content")
ACTION_CLASS="unknown"
RISK_POLICY_DECISION="require_approval"
REASONS+=("action-risk-classifier-failed-closed")
fi
APPROVAL_STATUS="auto_approved"
@@ -88,8 +109,14 @@ if [[ "$RISK_LEVEL" == "medium" ]]; then
APPROVAL_STATUS="policy_auto_approved_with_audit"
fi
if [[ "$RISK_LEVEL" == "high" ]]; then
if [[ "${CASAN_APPROVAL_STRICT:-0}" == "1" ]]; then
if [[ "$RISK_POLICY_DECISION" == "deny" ]]; then
APPROVAL_STATUS="actor_identity_required"
DECISION="denied"
REASONS+=("actor-identity-required")
elif [[ "$RISK_LEVEL" == "high" || "$RISK_LEVEL" == "critical" || "$RISK_POLICY_DECISION" == "require_approval" ]]; then
APPROVAL_STRICT_EFFECTIVE="${CASAN_APPROVAL_STRICT:-0}"
[[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ]] && APPROVAL_STRICT_EFFECTIVE="1"
if [[ "$APPROVAL_STRICT_EFFECTIVE" == "1" ]]; then
# Approval-identity mode (V20): an env-var approver is NOT enough — the
# reviewer must cryptographically SIGN this exact request and their role must
# be authorized for the action. SoD (actor != approver) still enforced.
@@ -147,7 +174,7 @@ fi
REASONS_JSON="$(printf '%s\n' "${REASONS[@]:-}" | python -c 'import json,sys; print(json.dumps([x for x in sys.stdin.read().splitlines() if x]))')"
# approver and output_hash are part of the hashed core so they cannot be
# silently mutated after the fact.
RECORD_CORE="$(printf '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' "$TIMESTAMP" "$TRACE_ID" "$ACTION_NAME" "$ACTOR" "$RISK_LEVEL" "$DECISION" "$APPROVAL_STATUS" "$APPROVER" "$INPUT_HASH" "$OUTPUT_HASH" "$PREV_HASH")"
RECORD_CORE="$(printf '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' "$TIMESTAMP" "$TRACE_ID" "$ACTION_NAME" "$ACTION_CLASS" "$ACTOR" "$RISK_LEVEL" "$RISK_FACTORS_JSON" "$EVIDENCE_REQUIREMENT" "$DECISION" "$APPROVAL_STATUS" "$APPROVER" "$INPUT_HASH" "$OUTPUT_HASH" "$PREV_HASH")"
RECORD_HASH="$(printf '%s' "$RECORD_CORE" | hash_text)"
TRACE_FILE="$TRACE_DIR/governance-$TRACE_ID.json"
@@ -160,18 +187,24 @@ TRACE_FILE="$TRACE_DIR/governance-$TRACE_ID.json"
# written (disk full, read-only, quota), there must be NO governed action without
# its accountability record — deny and empty the output rather than proceed.
if ! CASAN_GC_REASONS="$REASONS_JSON" python - "$TRACE_FILE" "$AUDIT_LOG" \
"$TIMESTAMP" "$TRACE_ID" "$ACTION_NAME" "$ACTOR" "$RISK_LEVEL" "$DECISION" \
"$APPROVAL_STATUS" "$APPROVER" "$INPUT_HASH" "$OUTPUT_HASH" "$PREV_HASH" "$RECORD_HASH" <<'PY'
"$TIMESTAMP" "$TRACE_ID" "$ACTION_NAME" "$ACTION_CLASS" "$ACTOR" "$RISK_LEVEL" \
"$RISK_FACTORS_JSON" "$EVIDENCE_REQUIREMENT" "$DECISION" "$APPROVAL_STATUS" \
"$APPROVER" "$INPUT_HASH" "$OUTPUT_HASH" "$PREV_HASH" "$RECORD_HASH" <<'PY'
import json, os, sys
(trace_file, audit_log, ts, trace_id, action, actor, risk, decision,
approval_status, approver, input_hash, output_hash, prev_hash, record_hash) = sys.argv[1:]
(trace_file, audit_log, ts, trace_id, action, action_class, actor, risk,
risk_factors_json, evidence_requirement, decision, approval_status, approver,
input_hash, output_hash, prev_hash, record_hash) = sys.argv[1:]
try:
reasons = json.loads(os.environ.get("CASAN_GC_REASONS") or "[]")
except ValueError:
reasons = []
rec = {
"schema_version": 2, "category": "runtime_control",
"timestamp": ts, "trace_id": trace_id, "harness": "H5-governance",
"action": action, "actor": actor, "risk_level": risk, "decision": decision,
"action": action, "action_class": action_class, "actor": actor,
"risk_level": risk, "effective_risk": risk,
"risk_factors": json.loads(risk_factors_json),
"evidence_requirement": evidence_requirement, "decision": decision,
"approval_status": approval_status, "approver": approver,
"input_hash": input_hash, "output_hash": output_hash,
"previous_record_hash": prev_hash, "record_hash": record_hash,
@@ -196,8 +229,43 @@ fi
# --- External anchor: cryptographically sign the new chain head ---
# A re-forged chain (recomputed hashes) changes the head; without the private
# key the attacker cannot produce a matching signature, so verification fails.
# Production note: the private key must live off-repo (KMS/HSM). It is local
# here only for self-contained demonstration.
# Development may use a local key for self-contained demonstration. Production
# refuses that path unless an explicit emergency override is visible in evidence.
PRODUCTION_PROFILE=0
[[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ]] && PRODUCTION_PROFILE=1
EMERGENCY_TRUST_OVERRIDE="${CASAN_TRUST_EMERGENCY_OVERRIDE:-0}"
TRUST_LOG="$CASAN_STATE_ROOT/logs/readiness/trust-capabilities.jsonl"
mkdir -p "$(dirname "$TRUST_LOG")"
if [[ "$PRODUCTION_PROFILE" == "1" && "$EMERGENCY_TRUST_OVERRIDE" != "1" ]]; then
TRUST_RC=0
TRUST_JSON="$(python3 "$KERNEL_CLI" trust-capabilities 2>/dev/null)" || TRUST_RC=$?
if [[ "$TRUST_RC" -ne 0 ]]; then
if [[ -n "$TRUST_JSON" ]]; then
printf '%s\n' "$TRUST_JSON" >> "$TRUST_LOG"
else
printf '{"ready":false,"severity":"critical","reason_codes":["production_trust_configuration_invalid"]}\n' >> "$TRUST_LOG"
fi
: > "$OUTPUT_FILE"
echo "GOVERNANCE_DENIED trace_id=$TRACE_ID reason=production_trust_root_unavailable" >&2
exit 2
fi
printf '%s\n' "$TRUST_JSON" >> "$TRUST_LOG"
if ! bash "$SCRIPT_DIR/sign-audit-head.sh" "$AUDIT_LOG" >/dev/null 2>&1; then
: > "$OUTPUT_FILE"
echo "GOVERNANCE_DENIED trace_id=$TRACE_ID reason=external_signing_failed" >&2
exit 2
fi
if ! bash "$SCRIPT_DIR/audit-ship-s3.sh" "$AUDIT_DIR/audit-head.txt" >/dev/null 2>&1; then
: > "$OUTPUT_FILE"
echo "GOVERNANCE_DENIED trace_id=$TRACE_ID reason=external_immutable_anchor_failed" >&2
exit 2
fi
else
if [[ "$PRODUCTION_PROFILE" == "1" ]]; then
printf '{"schema_version":"1.0.0","profile":"production","ready":false,"certifiable":false,"emergency_override":true,"severity":"critical","reason_codes":["emergency_local_trust_override_active"]}\n' >> "$TRUST_LOG"
echo "CRITICAL: emergency local trust override active; execution cannot be production-certified" >&2
fi
if command -v openssl >/dev/null 2>&1; then
# Private signing key lives OFF-REPO (default ~/.casan/audit-keys); only the
# public key is committed. Production: replace with KMS/HSM.
@@ -207,7 +275,7 @@ if command -v openssl >/dev/null 2>&1; then
AUDIT_PUB="$PUB_DIR/audit-public.pem"
mkdir -p "$PUB_DIR" "$PRIV_DIR"
if [[ ! -f "$AUDIT_PRIV" ]]; then
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_VERIFY_STRICT:-}" == "1" ]]; then
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" || "${CASAN_VERIFY_STRICT:-}" == "1" ]]; then
# SEC-02 (H-02): in enforced mode NEVER auto-generate a local signing key.
# A freshly-minted key next to the data lets any file-writer re-sign a forged
# head. Prod must provision the key out-of-band (KMS/HSM — see sign-audit-head.sh
@@ -229,6 +297,7 @@ if command -v openssl >/dev/null 2>&1; then
openssl dgst -sha256 -sign "$AUDIT_PRIV" -out "$AUDIT_DIR/audit-head.sig" "$AUDIT_DIR/audit-head.txt" 2>/dev/null || true
fi
fi
fi
if [[ "$DECISION" != "approved" ]]; then
: > "$OUTPUT_FILE"
@@ -21,22 +21,22 @@ while IFS= read -r raw || [[ -n "$raw" ]]; do
[[ "$line" =~ ^([A-Z0-9_]+)=(.*)$ ]] || fail "invalid_env_syntax"
key="${BASH_REMATCH[1]}"; value="${BASH_REMATCH[2]}"
case "$key" in
CASAN_PUBLIC_FQDN|CASAN_CP_HTTPS_PORT|CASAN_CP_TLS_DIR|CASAN_CP_OAUTH_ENV|CASAN_CP_RUNTIME_ENV|CASAN_CP_VAULT_ENV|CASAN_CP_STATE_DIR|CASAN_CP_OUTPUT_DIR|CASAN_CP_API_IMAGE|CASAN_CP_UI_IMAGE|CASAN_S3_BUCKET|CASAN_S3_PREFIX|CASAN_S3_REGION|CASAN_S3_RETENTION_DAYS|CASAN_S3_KMS_KEY_ID) export "$key=$value" ;;
CASAN_PUBLIC_FQDN|CASAN_CP_HTTPS_PORT|CASAN_CP_TLS_DIR|CASAN_CP_OAUTH_ENV|CASAN_CP_RUNTIME_ENV|CASAN_CP_VAULT_ENV|CASAN_CP_STATE_DIR|CASAN_CP_OUTPUT_DIR|CASAN_CP_IDP_PUBLIC_KEY|CASAN_CP_API_IMAGE|CASAN_CP_UI_IMAGE|CASAN_CP_OAUTH2_PROXY_IMAGE|CASAN_S3_BUCKET|CASAN_S3_PREFIX|CASAN_S3_REGION|CASAN_S3_RETENTION_DAYS|CASAN_S3_KMS_KEY_ID) export "$key=$value" ;;
*) fail "unexpected_env_key key=$key" ;;
esac
done < "$ENV_FILE"
required=(CASAN_PUBLIC_FQDN CASAN_CP_TLS_DIR CASAN_CP_OAUTH_ENV CASAN_CP_RUNTIME_ENV CASAN_CP_VAULT_ENV CASAN_CP_STATE_DIR CASAN_CP_OUTPUT_DIR CASAN_CP_API_IMAGE CASAN_CP_UI_IMAGE CASAN_S3_BUCKET CASAN_S3_REGION CASAN_S3_KMS_KEY_ID)
required=(CASAN_PUBLIC_FQDN CASAN_CP_TLS_DIR CASAN_CP_OAUTH_ENV CASAN_CP_RUNTIME_ENV CASAN_CP_VAULT_ENV CASAN_CP_STATE_DIR CASAN_CP_OUTPUT_DIR CASAN_CP_IDP_PUBLIC_KEY CASAN_CP_API_IMAGE CASAN_CP_UI_IMAGE CASAN_CP_OAUTH2_PROXY_IMAGE CASAN_S3_BUCKET CASAN_S3_REGION CASAN_S3_KMS_KEY_ID)
for key in "${required[@]}"; do [[ -n "${!key:-}" ]] || fail "missing_env key=$key"; done
case "$CASAN_PUBLIC_FQDN" in *localhost*|*127.0.0.1*|*example.com*|*replace-with*|*/*|[0-9]* ) fail "invalid_fqdn";; esac
[[ "$CASAN_PUBLIC_FQDN" == *.* ]] || fail "fqdn_required"
for image in "$CASAN_CP_API_IMAGE" "$CASAN_CP_UI_IMAGE"; do
for image in "$CASAN_CP_API_IMAGE" "$CASAN_CP_UI_IMAGE" "$CASAN_CP_OAUTH2_PROXY_IMAGE"; do
[[ "$image" =~ @sha256:[a-f0-9]{64}$ ]] || fail "image_must_be_digest_pinned image=$image"
done
pass "public FQDN and images are production-safe"
for file in "$CASAN_CP_TLS_DIR/tls.crt" "$CASAN_CP_TLS_DIR/tls.key" "$CASAN_CP_OAUTH_ENV" "$CASAN_CP_RUNTIME_ENV" "$CASAN_CP_VAULT_ENV"; do
for file in "$CASAN_CP_TLS_DIR/tls.crt" "$CASAN_CP_TLS_DIR/tls.key" "$CASAN_CP_IDP_PUBLIC_KEY" "$CASAN_CP_OAUTH_ENV" "$CASAN_CP_RUNTIME_ENV" "$CASAN_CP_VAULT_ENV"; do
[[ -s "$file" ]] || fail "missing_or_empty path=$file"
done
openssl x509 -in "$CASAN_CP_TLS_DIR/tls.crt" -noout >/dev/null || fail "invalid_tls_certificate"
@@ -45,6 +45,7 @@ openssl x509 -in "$CASAN_CP_TLS_DIR/tls.crt" -noout -checkhost "$CASAN_PUBLIC_FQ
cert_pub="$(openssl x509 -in "$CASAN_CP_TLS_DIR/tls.crt" -pubkey -noout | openssl pkey -pubin -outform DER | openssl dgst -sha256 | awk '{print $NF}')"
key_pub="$(openssl pkey -in "$CASAN_CP_TLS_DIR/tls.key" -pubout -outform DER | openssl dgst -sha256 | awk '{print $NF}')"
[[ "$cert_pub" == "$key_pub" ]] || fail "tls_key_does_not_match_certificate"
openssl rsa -pubin -in "$CASAN_CP_IDP_PUBLIC_KEY" -noout -modulus >/dev/null 2>&1 || fail "invalid_idp_rsa_public_key"
pass "TLS certificate is valid for at least 30 days"
value_of() { sed -n -E "s/^${1}=//p" "$CASAN_CP_OAUTH_ENV" | tail -1; }
@@ -56,8 +57,24 @@ done
[[ "$(value_of OAUTH2_PROXY_OIDC_ISSUER_URL)" == https://* ]] || fail "oidc_issuer_https_required"
[[ "$(value_of OAUTH2_PROXY_REDIRECT_URL)" == "https://$CASAN_PUBLIC_FQDN/oauth2/callback" ]] || fail "oidc_redirect_mismatch"
[[ "$(value_of OAUTH2_PROXY_COOKIE_SECURE)" == true ]] || fail "oidc_secure_cookie_required"
[[ "$(value_of OAUTH2_PROXY_SET_XAUTHREQUEST)" == true ]] || fail "oidc_xauthrequest_required"
[[ "$(value_of OAUTH2_PROXY_PASS_ACCESS_TOKEN)" == true ]] || fail "oidc_access_token_forwarding_required"
[[ "$(value_of OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER)" == true ]] || fail "oidc_authorization_header_forwarding_required"
pass "enterprise OIDC configuration"
runtime_value_of() { sed -n -E "s/^${1}=//p" "$CASAN_CP_RUNTIME_ENV" | tail -1; }
[[ "$(runtime_value_of CASAN_PROFILE)" == prod ]] || fail "runtime_profile_must_be_prod"
[[ "$(runtime_value_of CASAN_CP_AUTH_MODE)" == jwt ]] || fail "runtime_jwt_auth_required"
[[ "$(runtime_value_of CASAN_CP_JWT_ISSUER)" == "$(value_of OAUTH2_PROXY_OIDC_ISSUER_URL)" ]] || fail "runtime_oidc_issuer_mismatch"
[[ "$(runtime_value_of CASAN_CP_JWT_AUDIENCE)" == "$(value_of OAUTH2_PROXY_CLIENT_ID)" ]] || fail "runtime_oidc_audience_mismatch"
[[ "$(runtime_value_of CASAN_CP_JWT_PUBLIC_KEY_FILE)" == /run/casan-idp/idp-public.pem ]] || fail "runtime_idp_public_key_path_invalid"
[[ "$(runtime_value_of CASAN_SIGNING_PROVIDER)" == vault_kms ]] || fail "runtime_external_signing_required"
[[ "$(runtime_value_of CASAN_IMMUTABLE_ANCHOR_PROVIDER)" == s3_object_lock ]] || fail "runtime_immutable_anchor_required"
[[ -z "$(runtime_value_of CASAN_CP_TRUST_AUTH_PROXY)" ]] || fail "legacy_trusted_header_auth_forbidden"
clock_skew="$(runtime_value_of CASAN_CP_JWT_CLOCK_SKEW_SECONDS)"
[[ "$clock_skew" =~ ^[0-9]+$ && "$clock_skew" -le 300 ]] || fail "runtime_jwt_clock_skew_invalid"
pass "Control Plane verifies OIDC token identity cryptographically"
vault_addr="$(sed -n -E 's/^VAULT_ADDR=//p' "$CASAN_CP_VAULT_ENV" | tail -1)"
vault_token="$(sed -n -E 's/^VAULT_TOKEN=//p' "$CASAN_CP_VAULT_ENV" | tail -1)"
vault_cacert="$(sed -n -E 's/^VAULT_CACERT=//p' "$CASAN_CP_VAULT_ENV" | tail -1)"
@@ -88,7 +105,8 @@ COMPOSE="$ROOT/docker-compose.control-panel.yml"
CASAN_CP_TLS_DIR="$CASAN_CP_TLS_DIR" CASAN_CP_OAUTH_ENV="$CASAN_CP_OAUTH_ENV" \
CASAN_CP_RUNTIME_ENV="$CASAN_CP_RUNTIME_ENV" CASAN_CP_VAULT_ENV="$CASAN_CP_VAULT_ENV" \
CASAN_CP_STATE_DIR="$CASAN_CP_STATE_DIR" CASAN_CP_OUTPUT_DIR="$CASAN_CP_OUTPUT_DIR" \
CASAN_CP_API_IMAGE="$CASAN_CP_API_IMAGE" CASAN_CP_UI_IMAGE="$CASAN_CP_UI_IMAGE" \
CASAN_CP_IDP_PUBLIC_KEY="$CASAN_CP_IDP_PUBLIC_KEY" CASAN_CP_API_IMAGE="$CASAN_CP_API_IMAGE" \
CASAN_CP_UI_IMAGE="$CASAN_CP_UI_IMAGE" CASAN_CP_OAUTH2_PROXY_IMAGE="$CASAN_CP_OAUTH2_PROXY_IMAGE" \
docker compose -f "$COMPOSE" config >/dev/null || fail "compose_config_invalid"
pass "production compose config"
@@ -47,10 +47,15 @@ docker info >/dev/null 2>&1 || { echo "SANDBOX_CONTAINER_DOCKER_DOWN" >&2; exit
# rootful Docker daemon because a compromised daemon socket defeats container
# isolation. Local developer/test profiles may use a rootful daemon, but cannot
# claim that configuration as a hardened production runner.
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_SANDBOX_REQUIRE_ROOTLESS:-0}" == "1" ]]; then
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" \
|| "${CASAN_SANDBOX_REQUIRE_ROOTLESS:-0}" == "1" ]]; then
docker info --format '{{json .SecurityOptions}}' 2>/dev/null | grep -q 'rootless' \
|| { echo "SANDBOX_CONTAINER_ROOTLESS_REQUIRED" >&2; exit 2; }
fi
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ]]; then
[[ "$IMAGE" =~ @sha256:[a-f0-9]{64}$ ]] \
|| { echo "SANDBOX_CONTAINER_IMAGE_DIGEST_REQUIRED image=$IMAGE" >&2; exit 2; }
fi
WS_ABS="$(cd "$WORKSPACE" 2>/dev/null && pwd)" || { echo "SANDBOX_CONTAINER_BAD_WORKSPACE" >&2; exit 2; }
@@ -36,6 +36,37 @@ CPU_SECONDS="${CASAN_SANDBOX_CPU_SECONDS:-30}"
# check is the real gate; a container --pids-limit is the production backstop.
MAX_PROCS="${CASAN_SANDBOX_MAX_PROCS:-}"
TIMEOUT="${CASAN_SANDBOX_TIMEOUT:-30}"
SANDBOX_MODE="${CASAN_SANDBOX_MODE:-}"
STRICT_SANDBOX=0
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" \
|| "${CASAN_ENFORCEMENT_MODE:-}" == "enforce" || "${CASAN_SANDBOX_STRICT:-0}" == "1" ]]; then
STRICT_SANDBOX=1
fi
[[ -n "$SANDBOX_MODE" ]] || { if [[ "$STRICT_SANDBOX" == "1" ]]; then SANDBOX_MODE="container"; else SANDBOX_MODE="static"; fi; }
record_sandbox() { # decision reason backend capability-json
local decision="$1" reason="$2" backend="$3" capabilities="$4"
local log="$CASAN_STATE_ROOT/logs/sandbox/decisions.jsonl"
mkdir -p "$(dirname "$log")"
CASAN_SANDBOX_CAPABILITIES="$capabilities" python3 - "$log" "$decision" "$reason" "$backend" "${CASAN_EXECUTION_ID:-sandbox-$$}" <<'PY'
import json, os, sys
path, decision, reason, backend, execution_id = sys.argv[1:]
try:
capabilities = json.loads(os.environ.get("CASAN_SANDBOX_CAPABILITIES", "{}"))
except ValueError:
capabilities = {}
record = {
"schema_version": "1.0.0", "category": "runtime_control",
"policy_id": "casan.sandbox.backend", "decision": decision,
"reason_code": reason, "backend": backend, "execution_id": execution_id,
"capabilities": capabilities,
}
with open(path, "a", encoding="utf-8") as handle:
handle.write(json.dumps(record, sort_keys=True, separators=(",", ":")) + "\n")
handle.flush()
os.fsync(handle.fileno())
PY
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
@@ -54,12 +85,35 @@ if [[ "$#" -eq 0 ]]; then
exit 64
fi
# C6 production form: CASAN_SANDBOX_MODE=container runs under TRUE kernel
# C6 production form: CASAN_SANDBOX_MODE=container runs under kernel-backed
# isolation (sandbox-container.sh: --network=none --read-only --pids-limit …).
# Default stays the static-policy + ulimit scaffold so existing behaviour is
# unchanged. Falls back to the scaffold if Docker is unavailable.
if [[ "${CASAN_SANDBOX_MODE:-static}" == "container" ]] && command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
exec "$SCRIPT_DIR/sandbox-container.sh" --workspace "$WORKSPACE" --timeout "$TIMEOUT" -- "$@"
# A requested/required container backend never silently falls back.
if [[ "$SANDBOX_MODE" == "container" ]]; then
if [[ "${CASAN_SANDBOX_TEST_FORCE_UNAVAILABLE:-0}" != "1" ]] \
&& command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
record_sandbox "allow" "sandbox_container_selected" "docker" '{"network_disabled":true,"read_only_root":true,"workspace_write_restricted":true,"environment_filtered":true,"non_root":true,"resource_limits":true}'
exec "$SCRIPT_DIR/sandbox-container.sh" --workspace "$WORKSPACE" --timeout "$TIMEOUT" -- "$@"
fi
if [[ "$STRICT_SANDBOX" == "1" ]]; then
record_sandbox "deny" "sandbox_isolation_backend_unavailable" "none" '{"timeout_only":false}'
echo "SANDBOX_ISOLATION_REQUIRED backend=container reason=unavailable" >&2
exit 2
fi
if [[ "${CASAN_SANDBOX_ALLOW_STATIC_FALLBACK:-0}" != "1" ]]; then
record_sandbox "deny" "sandbox_fallback_not_approved" "none" '{}'
echo "SANDBOX_FALLBACK_REQUIRES_EXPLICIT_DEVELOPMENT_APPROVAL" >&2
exit 2
fi
record_sandbox "observe_only" "sandbox_static_fallback_development_only" "static_rlimit" '{"network_disabled":false,"read_only_root":false,"workspace_write_restricted":false,"environment_filtered":false,"non_root":false,"resource_limits":true}'
echo "HIGH: container sandbox unavailable; explicit development static fallback is not production isolation" >&2
elif [[ "$SANDBOX_MODE" != "static" ]]; then
record_sandbox "deny" "sandbox_backend_unknown" "$SANDBOX_MODE" '{}'
echo "SANDBOX_BACKEND_UNKNOWN mode=$SANDBOX_MODE" >&2
exit 2
elif [[ "$STRICT_SANDBOX" == "1" ]]; then
record_sandbox "deny" "sandbox_static_forbidden_in_enforce_mode" "static_rlimit" '{"network_disabled":false,"read_only_root":false}'
echo "SANDBOX_STATIC_FORBIDDEN_IN_ENFORCE_MODE" >&2
exit 2
fi
CMD_STR="$*"
@@ -96,6 +150,7 @@ done < <(printf '%s\n' "$CMD_STR" | grep -oE '>>?[[:space:]]*[^[:space:];|&]+' |
# ── 2. Runtime rlimits + wall-clock timeout ─────────────────────────────────
casan_log debug sandbox "SANDBOX_RUN workspace=$WS_ABS file_kb=$MAX_FILE_KB cpu=$CPU_SECONDS procs=$MAX_PROCS timeout=$TIMEOUT"
record_sandbox "allow" "sandbox_static_policy_selected" "static_rlimit" '{"network_disabled":false,"read_only_root":false,"workspace_write_restricted":false,"environment_filtered":false,"non_root":false,"resource_limits":true}'
(
ulimit -f "$((MAX_FILE_KB * 2))" 2>/dev/null || true # ulimit -f is in 512-byte blocks
ulimit -t "$CPU_SECONDS" 2>/dev/null || true
@@ -45,14 +45,24 @@ with open(path, encoding="utf-8") as f:
if not line.strip():
continue
record = json.loads(line)
core = "|".join([
record.get("timestamp",""), record.get("trace_id",""),
record.get("action",""), record.get("actor",""),
record.get("risk_level",""), record.get("decision",""),
record.get("approval_status",""), record.get("approver",""),
record.get("input_hash",""), record.get("output_hash",""),
previous,
])
if int(record.get("schema_version", 1)) >= 2:
core = "|".join([
record.get("timestamp",""), record.get("trace_id",""),
record.get("action",""), record.get("action_class",""),
record.get("actor",""), record.get("risk_level",""),
json.dumps(record.get("risk_factors", {}), sort_keys=True, separators=(",", ":")),
record.get("evidence_requirement",""), record.get("decision",""),
record.get("approval_status",""), record.get("approver",""),
record.get("input_hash",""), record.get("output_hash",""), previous,
])
else:
core = "|".join([
record.get("timestamp",""), record.get("trace_id",""),
record.get("action",""), record.get("actor",""),
record.get("risk_level",""), record.get("decision",""),
record.get("approval_status",""), record.get("approver",""),
record.get("input_hash",""), record.get("output_hash",""), previous,
])
previous = hashlib.sha256(core.encode()).hexdigest()
print(previous)
PY
@@ -68,6 +78,10 @@ printf '%s' "$HEAD_HASH" > "$HEAD_FILE"
# ── Sign the head file ────────────────────────────────────────────────────
VAULT_KMS="$SCRIPT_DIR/vault-kms.sh"
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ]]; then
[[ "${VAULT_ADDR:-}" == https://* ]] || { echo "SIGN_AUDIT_HEAD_FAIL reason=vault_https_required_in_prod" >&2; exit 1; }
fi
if [[ -n "${VAULT_ADDR:-}" && -n "${VAULT_TOKEN:-}" ]] && \
curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then
# KMS path — sign via Vault Transit, export public key
@@ -103,7 +117,7 @@ PY
fi
else
# Fallback — local key (dev environment without Vault)
if [[ "${CASAN_PROFILE:-}" == "prod" ]]; then
if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_PROFILE:-}" == "production" || "${CASAN_PROFILE:-}" == "strict" ]]; then
echo "SIGN_AUDIT_HEAD_FAIL reason=vault_kms_required_in_prod" >&2
exit 1
fi
@@ -35,21 +35,28 @@ with open(path, encoding="utf-8") as f:
f"AUDIT_CHAIN_BROKEN line={line_no} expected_previous={previous} actual_previous={expected_previous}"
)
core = "|".join(
[
record.get("timestamp", ""),
record.get("trace_id", ""),
record.get("action", ""),
record.get("actor", ""),
record.get("risk_level", ""),
record.get("decision", ""),
record.get("approval_status", ""),
record.get("approver", ""),
record.get("input_hash", ""),
record.get("output_hash", ""),
if int(record.get("schema_version", 1)) >= 2:
core = "|".join([
record.get("timestamp", ""), record.get("trace_id", ""),
record.get("action", ""), record.get("action_class", ""),
record.get("actor", ""), record.get("risk_level", ""),
json.dumps(record.get("risk_factors", {}), sort_keys=True, separators=(",", ":")),
record.get("evidence_requirement", ""), record.get("decision", ""),
record.get("approval_status", ""), record.get("approver", ""),
record.get("input_hash", ""), record.get("output_hash", ""),
expected_previous,
]
)
])
else:
core = "|".join(
[
record.get("timestamp", ""), record.get("trace_id", ""),
record.get("action", ""), record.get("actor", ""),
record.get("risk_level", ""), record.get("decision", ""),
record.get("approval_status", ""), record.get("approver", ""),
record.get("input_hash", ""), record.get("output_hash", ""),
expected_previous,
]
)
expected_hash = hashlib.sha256(core.encode()).hexdigest()
actual_hash = record.get("record_hash", "")
if expected_hash != actual_hash:
@@ -40,8 +40,23 @@ import sys
import time
import uuid
SCHEMA_VERSION = "20.1"
ADAPTER_DEFAULT_VERSION = "20.1.0"
HARNESS_PACKAGE_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if HARNESS_PACKAGE_ROOT not in sys.path:
sys.path.insert(0, HARNESS_PACKAGE_ROOT)
from kernel.adapters import AgenticBridgeAdapter
from kernel.contracts import validate_bundle
from kernel.policy import (
H2GateContext,
evaluate_failure_policy,
evaluate_h2_gate,
evaluate_registry_configuration,
evaluate_risk,
evaluate_trust_capabilities,
)
SCHEMA_VERSION = "20.2"
ADAPTER_DEFAULT_VERSION = "20.2.0"
# ── Certification strength ranking (higher == stronger) ──────────────────────
STRENGTH_RANK = {
@@ -77,6 +92,20 @@ TOOL_REGISTRY_ACTION = {
"str_replace_editor": "write_file",
}
REGISTRY_ACTION_BY_CLASS = {
"write": "write_file",
"delete": "delete_file",
"database_mutation": "db_write",
"migration": "migration",
"deployment": "deploy",
"release": "deploy",
"credential_access": "credential_access",
"identity_permission_modification": "identity_change",
"external_network_side_effect": "external_api",
"infrastructure_modification": "deploy",
"unknown": "unknown_tool",
}
# ─────────────────────────────────────────────────────────────────────────────
# Path resolution (mirrors scripts/bash/casan-paths.sh so state lands in the
@@ -145,6 +174,12 @@ def trace_event_dir():
return d
def kernel_trace_dir():
d = os.path.join(state_root(), "logs", "kernel")
os.makedirs(d, exist_ok=True)
return d
def metrics_log():
override = os.environ.get("CASAN_TELEMETRY_METRICS_LOG") or os.environ.get(
"CASAN_METRICS_LOG"
@@ -612,28 +647,52 @@ def h4_scan(text, mode="input"):
pass
def h2_registry_gate(action, idempotency_key):
"""H2 tool-registry gate for a mapped side-effect action."""
script = os.path.join(gates_dir(), "tool-registry-gate.sh")
if not os.path.exists(script) or not bash_available():
return True, "h2_gate_missing"
env = dict(os.environ)
env["CASAN_IDEMPOTENCY_KEY"] = idempotency_key
def h2_registry_gate(action, tool, actor, execution_id, idempotency_key):
"""Return a structured H2 policy decision for every dependency outcome."""
script = os.environ.get("CASAN_H2_GATE_PATH") or os.path.join(
gates_dir(), "tool-registry-gate.sh")
context = H2GateContext(
mode=enforcement_mode(),
actor=actor or "",
action=action,
tool=tool or action,
execution_id=execution_id,
enforcement_path="agentic_bridge.pre_tool.h2_registry",
idempotency_key=idempotency_key,
timeout_seconds=internal_timeout(),
)
return evaluate_h2_gate(
script,
context,
env=os.environ,
bash_path=bash_bin(),
)
def h5_governance_gate(action, content):
"""Invoke the existing H5 gate for action-floor approval decisions."""
script = os.path.join(gates_dir(), "governance-check.sh")
if not os.path.isfile(script) or not bash_available():
return {"approved": False, "reason_code": "h5_gate_unavailable", "detail": "governance gate unavailable"}
tmpin = os.path.join(sessions_dir(), ".h5-in-%s" % uuid.uuid4().hex[:8])
tmpout = os.path.join(sessions_dir(), ".h5-out-%s" % uuid.uuid4().hex[:8])
try:
proc = subprocess.run(
[bash_bin(), script, action],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=internal_timeout(),
env=env,
)
if proc.returncode == 0:
return True, "h2_ok"
return False, "h2_denied"
except subprocess.TimeoutExpired:
return False, "h2_internal_timeout"
except (OSError, ValueError) as exc:
return False, "h2_error:%s" % exc
with open(tmpin, "w", encoding="utf-8") as handle:
handle.write(content or "")
rc, stdout, stderr = _run_gate([bash_bin(), script, tmpin, tmpout, action])
if rc == 0 and "GOVERNANCE_APPROVED" in stdout:
return {"approved": True, "reason_code": "h5_approval_verified", "detail": stdout.strip()}
if rc == 124:
return {"approved": False, "reason_code": "h5_gate_timeout", "detail": "governance gate timed out"}
if rc == 125:
return {"approved": False, "reason_code": "h5_gate_internal_error", "detail": stderr}
return {"approved": False, "reason_code": "h5_approval_required", "detail": stderr or stdout}
finally:
for path in (tmpin, tmpout):
try:
os.unlink(path)
except OSError:
pass
# ─────────────────────────────────────────────────────────────────────────────
@@ -692,6 +751,8 @@ def write_trace_events(rec, evidence):
"kind": evidence.get("kind"),
"decision": evidence.get("decision"),
"detail": evidence.get("detail"),
"category": evidence.get("category"),
"facts": evidence.get("facts", {}),
"certification_strength": rec.get(
"certification_strength"),
},
@@ -700,13 +761,16 @@ def write_trace_events(rec, evidence):
event, ensure_ascii=False, separators=(",", ":")) + "\n")
def add_evidence(rec, h, kind, decision, detail):
def add_evidence(rec, h, kind, decision, detail, facts=None, category="runtime_control"):
evidence = {
"evidence_id": "%s:%s" % (rec.get("trace_id", "trace"), len(rec.get("evidence", [])) + 1),
"h": h,
"category": category,
"kind": kind,
"decision": decision,
"at": now_iso(),
"detail": redact(detail, 160),
"facts": facts if isinstance(facts, dict) else {},
}
rec.setdefault("evidence", []).append(evidence)
write_trace_events(rec, evidence)
@@ -828,6 +892,27 @@ def write_h6_record(rec, status, quality, warnings, missing):
return record
def write_kernel_bundle(rec):
"""Dual-emit the canonical contract without changing legacy trace readers."""
event = dict(rec)
event.update({
"mode": enforcement_mode(),
"execution_status": rec.get("execution_status", "unknown"),
"assurance_status": rec.get("assurance_status", "unknown"),
"environment": os.environ.get("CASAN_PROFILE", "development"),
"completed_at": rec.get("finalized_at") or now_iso(),
"policy_decisions": rec.get("policy_decisions", []),
})
bundle = AgenticBridgeAdapter().map_execution(event)
errors = validate_bundle(bundle)
if errors:
raise ValueError("kernel_contract_invalid:%s" % ",".join(errors))
path = os.path.join(kernel_trace_dir(), "%s.json" % rec.get("trace_id"))
atomic_write_json(path, bundle)
rec["kernel_contract_ref"] = path
return path
def write_trace_file(rec, certified, reasons):
path = os.path.join(trace_dir(), "agentic-%s.json" % rec.get("trace_id"))
payload = {
@@ -851,6 +936,9 @@ def write_trace_file(rec, certified, reasons):
"failures": rec.get("failures", 0),
"certified": certified,
"certification_reasons": reasons,
"execution_outcome": rec.get("execution_status"),
"assurance_outcome": rec.get("assurance_status"),
"kernel_contract_ref": rec.get("kernel_contract_ref"),
"evidence": rec.get("evidence", []),
}
atomic_write_json(path, payload)
@@ -886,6 +974,8 @@ def op_begin(req):
"client": client,
"client_version": req.get("client_version"),
"adapter_version": req.get("adapter_version") or ADAPTER_DEFAULT_VERSION,
"actor": req.get("actor") or os.environ.get("CASAN_ACTOR") or os.environ.get("CASAN_AGENT") or "",
"mode": enforcement_mode(),
"project_root": project,
"project_id": project_id(project),
"session_id_hash": salted_hash(req.get("session")),
@@ -900,8 +990,10 @@ def op_begin(req):
"state": "Admitted",
"tool_calls": 0,
"failures": 0,
"side_effect_failures": 0,
"retries": 0,
"evidence": [],
"policy_decisions": [],
"telemetry": {},
"bypass_signal": False,
"finalized": False,
@@ -1012,30 +1104,109 @@ def op_pre_tool(req):
# record the degradation but do not block the developer.
add_evidence(rec, "H4", "pre-tool", "degraded", "%s:gate_unavailable_no_bash" % tool)
# H2 registry gate for mapped side-effect actions. Opt-in via
# CASAN_AGENTIC_H2_REGISTRY=1: the tool-registry is keyed on NAMED CASAN
# agent identities, which the transparent developer flow does not carry, so
# enabling it unconditionally would deny every write. The always-on H2
# equivalent for this flow is the admission gate above (a side effect
# without a valid admission is denied). Managed deployments that define
# agent identities can turn the registry gate on for defence in depth.
registry_on = os.environ.get("CASAN_AGENTIC_H2_REGISTRY", "0") in ("1", "true", "yes")
if side_effect and registry_on and enforcement_mode() == "enforce":
action = TOOL_REGISTRY_ACTION.get((tool or "").strip().lower())
if action:
key = plain_hash("%s|%s|%s" % (rec["trace_id"], tool, redact(tool_input_text)))[:24]
ok, reason = h2_registry_gate(action, key)
if not ok:
add_evidence(rec, "H2", "pre-tool", "deny", "%s:%s" % (tool, reason))
save_admission(rec)
return _base_response("pre-tool", "deny", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason=reason)
tool_input_string = tool_input_text if isinstance(tool_input_text, str) else json.dumps(
tool_input_text, ensure_ascii=False, sort_keys=True) if tool_input_text is not None else ""
# Native hook payloads commonly wrap a shell command in {"command": ...}.
# Classify the actual command while retaining the complete serialized input
# for scanning, governance evidence and hashing.
risk_command = tool_input_string
if isinstance(tool_input_text, dict) and isinstance(tool_input_text.get("command"), str):
risk_command = tool_input_text["command"]
risk = evaluate_risk(
action=str(req.get("action") or ""),
tool=tool,
resource=str(req.get("resource") or ""),
command=risk_command,
content=tool_input_string,
actor=rec.get("actor", ""),
environment=os.environ.get("CASAN_PROFILE", "development"),
)
# The canonical classifier can prove a shell command read-only; unknown
# shell commands remain side-effecting/high-risk by construction.
side_effect = bool(risk["side_effecting"])
action = REGISTRY_ACTION_BY_CLASS.get(risk["action_class"], TOOL_REGISTRY_ACTION.get((tool or "").strip().lower(), "unknown_tool"))
rec["last_tool"] = tool
rec["last_action"] = action
rec["last_risk"] = risk
rec.setdefault("policy_decisions", []).append(risk)
add_evidence(
rec, "H5", "action-risk", risk["decision"],
"class=%s effective=%s" % (risk["action_class"], risk["effective_risk"]),
facts=risk,
)
if side_effect and risk["decision"] == "deny":
reason = risk["reason_codes"][-1]
add_evidence(rec, "H5", "pre-tool", "deny", reason, facts=risk)
save_admission(rec)
return _base_response("pre-tool", "deny", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason=reason,
policy_decision=risk)
if side_effect and risk["decision"] == "require_approval":
approval = h5_governance_gate(action, tool_input_string)
add_evidence(
rec, "H5", "approval", "allow" if approval["approved"] else "require_approval",
approval["reason_code"], facts={"risk": risk, "approval": approval},
)
if not approval["approved"]:
save_admission(rec)
return _base_response("pre-tool", "require_approval", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason=approval["reason_code"],
policy_decision=risk)
registry = evaluate_registry_configuration(
enforcement_mode(),
os.environ.get("CASAN_PROFILE", "development"),
os.environ.get("CASAN_AGENTIC_H2_REGISTRY") if "CASAN_AGENTIC_H2_REGISTRY" in os.environ else None,
)
if side_effect and not registry["valid"]:
add_evidence(rec, "H2", "registry-configuration", "deny", registry["reason_code"], facts=registry)
save_admission(rec)
return _base_response("pre-tool", "deny", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason=registry["reason_code"])
if side_effect and registry["unsafe_bypass"]:
rec["integration_mode"] = "observed_only"
rec["certification_strength"] = "observed_only"
add_evidence(rec, "H2", "registry-configuration", "degraded", registry["reason_code"], facts=registry)
if side_effect and registry["enabled"]:
key = plain_hash("%s|%s|%s" % (rec["trace_id"], tool, redact(tool_input_text)))[:24]
h2_decision = h2_registry_gate(action, tool, rec.get("actor", ""), rec["trace_id"], key)
rec.setdefault("policy_decisions", []).append(h2_decision)
add_evidence(
rec, "H2", "tool-registry", h2_decision["decision"],
h2_decision["reason_code"], facts=h2_decision,
)
if not h2_decision["execution_allowed"]:
save_admission(rec)
return _base_response("pre-tool", "deny", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason=h2_decision["reason_code"],
policy_decision=h2_decision)
if not h2_decision["certifiable"]:
rec["integration_mode"] = "observed_only"
rec["certification_strength"] = "observed_only"
add_evidence(rec, "H2/H4", "pre-tool", "allow",
"tool=%s side_effect=%s" % (tool, side_effect))
"tool=%s side_effect=%s action=%s" % (tool, side_effect, action),
facts={"action_risk": risk, "registry": registry})
save_admission(rec)
return _base_response("pre-tool", "allow", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason="allowed")
response = _base_response("pre-tool", "allow", admission_id=admission_id,
trace_id=rec.get("trace_id"), reason="allowed",
policy_decision=risk)
if registry["unsafe_bypass"]:
response["warnings"].append("HIGH: H2 registry bypass active in development/test; execution is not certifiable")
return response
def single_step_failure_policy(side_effect):
decision = evaluate_failure_policy(
side_effecting=side_effect,
mode=enforcement_mode(),
profile=os.environ.get("CASAN_PROFILE", "development"),
configured=os.environ.get("CASAN_SINGLE_STEP_FAILURE_POLICY"),
)
return decision["failure_policy"], decision["source"]
def op_post_tool(req):
@@ -1045,11 +1216,52 @@ def op_post_tool(req):
reason="no_admission")
rec["tool_calls"] = rec.get("tool_calls", 0) + 1
status = req.get("status") or "success"
if status in ("error", "timeout"):
risk = evaluate_risk(
action=str(rec.get("last_action") or ""),
tool=str(req.get("tool") or rec.get("last_tool") or ""),
command=str(req.get("command") or ""),
actor=rec.get("actor", ""),
environment=os.environ.get("CASAN_PROFILE", "development"),
)
side_effect = bool(risk["side_effecting"])
if status in ("error", "timeout", "denied", "failed"):
rec["failures"] = rec.get("failures", 0) + 1
if side_effect:
rec["side_effect_failures"] = rec.get("side_effect_failures", 0) + 1
policy, source = single_step_failure_policy(side_effect)
rec["failure_policy"] = policy
rec["execution_status"] = "failed"
rec["assurance_status"] = "failed"
add_evidence(
rec, "H6", "failure-policy", policy,
"tool=%s status=%s policy=%s" % (req.get("tool"), status, policy),
facts={
"command_executed": status not in ("denied",),
"command_outcome": status,
"telemetry_recorded": True,
"assurance_may_continue": policy == "record_only",
"failure_policy": policy,
"policy_source": source,
"side_effecting": side_effect,
},
)
add_evidence(rec, "H5", "post-tool", status,
"tool=%s dur_ms=%s result=%s" % (req.get("tool"), req.get("duration_ms"),
redact(req.get("result"), 80)),
facts={"risk": risk, "failure_policy": policy})
save_admission(rec)
decision = "recorded" if policy == "record_only" else policy
return _base_response(
"post-tool", decision, admission_id=rec["admission_id"],
trace_id=rec.get("trace_id"), reason="tool_failed:%s" % policy,
execution_outcome="failed", assurance_outcome="failed",
assurance_may_continue=policy == "record_only",
)
rec["execution_status"] = rec.get("execution_status") or "success"
add_evidence(rec, "H5", "post-tool", status,
"tool=%s dur_ms=%s result=%s" % (req.get("tool"), req.get("duration_ms"),
redact(req.get("result"), 80)))
redact(req.get("result"), 80)),
facts={"risk": risk, "command_outcome": status, "telemetry_recorded": True})
save_admission(rec)
return _base_response("post-tool", "recorded", admission_id=rec["admission_id"],
trace_id=rec.get("trace_id"), reason="evidence_appended")
@@ -1092,6 +1304,10 @@ def op_finalize(req):
rec["summary_hash"] = salted_hash(req.get("assistant_summary"))
stop_reason = req.get("stop_reason") or "completed"
status = "success" if stop_reason in ("completed", "max_turns") else "failed"
if rec.get("failures", 0) > 0:
# Telemetry success is not execution success. A failed tool remains a
# failed execution even if the client later emits Stop(completed).
status = "failed"
observed_harnesses = {
harness
@@ -1110,6 +1326,8 @@ def op_finalize(req):
# H3/H5/H7 finalize controls: run the H4 output filter over the assistant
# summary as the closing verification control.
reasons = []
if rec.get("failures", 0) > 0:
reasons.append("tool_execution_failed")
if req.get("assistant_summary"):
h4 = h4_scan(req.get("assistant_summary"), "output")
if h4 == "ok":
@@ -1158,7 +1376,17 @@ def op_finalize(req):
reasons.append("coverage_bypass")
if status != "success":
certified = False
reasons.append("stop_%s" % stop_reason)
failure_reason = "failed_tool_outcome" if rec.get("failures", 0) > 0 else "stop_%s" % stop_reason
if failure_reason not in reasons:
reasons.append(failure_reason)
trust = evaluate_trust_capabilities()
add_evidence(
rec, "H7", "trust-capability", "pass" if trust["certifiable"] else "degraded",
",".join(trust["reason_codes"]), facts=trust, category="readiness_check",
)
if trust["production"] and not trust["certifiable"]:
certified = False
reasons.extend(reason for reason in trust["reason_codes"] if reason not in reasons)
if not certified and not reasons:
reasons.append("unknown")
if certified:
@@ -1168,12 +1396,15 @@ def op_finalize(req):
rec["certified"] = certified
rec["finalized"] = True
rec["finalized_at"] = now_iso()
rec["execution_status"] = status
rec["assurance_status"] = "passed" if certified else "failed" if rec.get("failures", 0) else "degraded"
add_evidence(
rec,
"H7",
"certification",
"certified" if certified else "non_certified",
",".join(reasons),
category="certification_claim",
)
quality, warnings, missing = classify_telemetry(rec)
@@ -1185,13 +1416,26 @@ def op_finalize(req):
",".join(warnings) if warnings else "provider_usage_complete",
)
h6_record = write_h6_record(rec, status, quality, warnings, missing)
try:
write_kernel_bundle(rec)
except (OSError, ValueError, TypeError) as exc:
certified = False
rec["certified"] = False
rec["state"] = "NonCertified"
rec["assurance_status"] = "failed"
if "kernel_contract_emission_failed" not in reasons:
reasons.append("kernel_contract_emission_failed")
add_evidence(rec, "H7", "kernel-contract", "failed",
"kernel_contract_emission_failed:%s" % type(exc).__name__)
trace_path = write_trace_file(rec, certified, reasons)
save_admission(rec)
resp = _base_response("finalize", "certified" if certified else "non_certified",
admission_id=rec["admission_id"], trace_id=rec.get("trace_id"),
certification_strength=strength, telemetry_quality=quality,
reason=",".join(reasons))
reason=",".join(reasons), execution_outcome=status,
assurance_outcome=rec.get("assurance_status"),
certification_outcome="certified" if certified else "non_certified")
resp["warnings"].extend(warnings)
resp["context"] = "trace=%s certified=%s" % (os.path.basename(trace_path), certified)
report_url = dashboard_url(rec.get("trace_id"))
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""CLI boundary for shell/native runtimes to consume Assurance Kernel policy."""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
HARNESS_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(HARNESS_ROOT))
from kernel.adapters import NativeHarnessAdapter # noqa: E402
from kernel.contracts import validate_bundle # noqa: E402
from kernel.policy import ( # noqa: E402
H2GateContext,
evaluate_h2_gate,
evaluate_registry_configuration,
evaluate_risk,
evaluate_trust_capabilities,
)
def _print(payload: object) -> None:
print(json.dumps(payload, sort_keys=True, separators=(",", ":")))
def _read_content(path: str | None) -> str:
if not path:
return ""
with open(path, encoding="utf-8") as handle:
return handle.read()
def _append_jsonl(path: str, payload: object) -> None:
target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True)
data = (json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
fd = os.open(str(target), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
try:
try:
import fcntl
fcntl.flock(fd, fcntl.LOCK_EX)
except (ImportError, OSError):
pass
os.write(fd, data)
os.fsync(fd)
finally:
os.close(fd)
def cmd_risk(args: argparse.Namespace) -> int:
payload = evaluate_risk(
action=args.action,
tool=args.tool,
resource=args.resource,
command=args.command,
content=_read_content(args.content_file),
actor=args.actor,
environment=args.environment,
)
_print(payload)
return 0
def cmd_registry(args: argparse.Namespace) -> int:
payload = evaluate_registry_configuration(args.mode, args.profile, args.explicit)
if args.evidence_log:
_append_jsonl(args.evidence_log, payload)
_print(payload)
return 0 if payload["valid"] else 2
def cmd_h2(args: argparse.Namespace) -> int:
context = H2GateContext(
mode=args.mode,
actor=args.actor,
action=args.action,
tool=args.tool,
execution_id=args.execution_id,
enforcement_path=args.enforcement_path,
idempotency_key=args.idempotency_key,
timeout_seconds=args.timeout,
)
payload = evaluate_h2_gate(args.gate, context, bash_path=args.bash)
if args.evidence_log:
_append_jsonl(args.evidence_log, payload)
_print(payload)
return 0 if payload["execution_allowed"] else 2
def cmd_trust(_args: argparse.Namespace) -> int:
payload = evaluate_trust_capabilities()
_print(payload)
return 0 if payload["ready"] else 2
def cmd_native(args: argparse.Namespace) -> int:
event = json.load(sys.stdin) if args.event_file == "-" else json.load(open(args.event_file, encoding="utf-8"))
payload = NativeHarnessAdapter().map_execution(event)
errors = validate_bundle(payload)
if errors:
_print({"valid": False, "errors": errors, "bundle": payload})
return 2
_print(payload)
return 0
def parser() -> argparse.ArgumentParser:
root = argparse.ArgumentParser()
sub = root.add_subparsers(dest="command", required=True)
risk = sub.add_parser("risk")
for name in ("action", "tool", "resource", "command", "actor", "environment"):
risk.add_argument(f"--{name}", default="")
risk.add_argument("--content-file")
risk.set_defaults(func=cmd_risk)
registry = sub.add_parser("registry-config")
registry.add_argument("--mode", default="observe")
registry.add_argument("--profile", default="development")
registry.add_argument("--explicit")
registry.add_argument("--evidence-log")
registry.set_defaults(func=cmd_registry)
h2 = sub.add_parser("h2-gate")
h2.add_argument("--gate", required=True)
h2.add_argument("--mode", choices=("observe", "enforce"), required=True)
h2.add_argument("--actor", default="")
h2.add_argument("--action", required=True)
h2.add_argument("--tool", required=True)
h2.add_argument("--execution-id", required=True)
h2.add_argument("--enforcement-path", required=True)
h2.add_argument("--idempotency-key", required=True)
h2.add_argument("--timeout", type=float, default=8.0)
h2.add_argument("--bash", default="bash")
h2.add_argument("--evidence-log")
h2.set_defaults(func=cmd_h2)
trust = sub.add_parser("trust-capabilities")
trust.set_defaults(func=cmd_trust)
native = sub.add_parser("adapt-native")
native.add_argument("event_file")
native.set_defaults(func=cmd_native)
return root
def main() -> int:
args = parser().parse_args()
return int(args.func(args))
if __name__ == "__main__":
raise SystemExit(main())
@@ -199,6 +199,9 @@ def _check(
) -> dict[str, Any]:
return {
"gate": gate,
"legacy_gate": gate,
"category": "readiness_check",
"check_id": "ReadinessCheck.%s" % gate,
"title": title,
"status": status,
"summary": summary,
@@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""Deterministic unit and cross-runtime conformance tests for the kernel."""
from __future__ import annotations
import os
import stat
import sys
import tempfile
import unittest
from copy import deepcopy
from pathlib import Path
from unittest.mock import patch
HARNESS_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(HARNESS_ROOT))
from kernel.adapters import AgenticBridgeAdapter, NativeHarnessAdapter
from kernel.contracts import validate_bundle
from kernel.policy import (
H2GateContext,
classify_action,
evaluate_failure_policy,
evaluate_h2_gate,
evaluate_registry_configuration,
evaluate_risk,
evaluate_trust_capabilities,
)
from kernel.taxonomy import CertificationClaim, ReadinessCheck, ReportDimension, RuntimeControl, same_legacy_label
from kernel.supervision import RuntimeCapabilities, negotiate_intervention
class RiskFloorTests(unittest.TestCase):
def test_benign_deploy_has_high_floor_and_requires_approval(self) -> None:
decision = evaluate_risk(action="deploy", content="publish a harmless documentation update", actor="alice")
self.assertEqual(decision["action_class"], "deployment")
self.assertEqual(decision["risk_factors"]["content_risk"], "low")
self.assertEqual(decision["effective_risk"], "high")
self.assertEqual(decision["decision"], "require_approval")
def test_effective_risk_is_maximum_of_all_factors(self) -> None:
decision = evaluate_risk(action="read", resource="/.ssh/id_rsa", actor="alice")
self.assertEqual(decision["risk_factors"]["action_risk"], "high")
self.assertEqual(decision["risk_factors"]["resource_risk"], "high")
self.assertEqual(decision["effective_risk"], "high")
def test_missing_actor_denies_high_impact_action(self) -> None:
decision = evaluate_risk(action="migration", content="apply schema", actor="")
self.assertEqual(decision["decision"], "deny")
self.assertIn("actor_identity_required", decision["reason_codes"])
def test_read_only_shell_command_is_not_side_effecting(self) -> None:
decision = classify_action(tool="Bash", command="git status --short")
self.assertEqual(decision["action_class"], "read_only")
self.assertFalse(decision["side_effecting"])
class RegistryConfigurationTests(unittest.TestCase):
def test_enforce_defaults_registry_on(self) -> None:
result = evaluate_registry_configuration("enforce", "development", None)
self.assertTrue(result["enabled"])
self.assertEqual(result["reason_code"], "h2_registry_default_enabled")
def test_development_bypass_is_visible_and_non_silent(self) -> None:
result = evaluate_registry_configuration("enforce", "test", "0")
self.assertFalse(result["enabled"])
self.assertTrue(result["unsafe_bypass"])
self.assertEqual(result["severity"], "high")
def test_production_bypass_is_rejected(self) -> None:
result = evaluate_registry_configuration("enforce", "production", "0")
self.assertFalse(result["valid"])
self.assertEqual(result["reason_code"], "h2_registry_bypass_forbidden")
class FailurePolicyTests(unittest.TestCase):
def test_all_supported_policies_are_explicit(self) -> None:
for configured in ("halt", "quarantine", "require_approval", "record_only"):
result = evaluate_failure_policy(
side_effecting=False, mode="observe", profile="test", configured=configured,
)
self.assertEqual(result["failure_policy"], configured)
self.assertEqual(result["execution_result"], "failed")
self.assertEqual(result["telemetry_result"], "recorded")
def test_production_side_effect_cannot_be_record_only(self) -> None:
result = evaluate_failure_policy(
side_effecting=True, mode="enforce", profile="production", configured="record_only",
)
self.assertEqual(result["failure_policy"], "halt")
self.assertFalse(result["valid"])
self.assertEqual(result["source"], "production_record_only_forbidden")
def test_invalid_policy_fails_closed(self) -> None:
result = evaluate_failure_policy(
side_effecting=True, mode="enforce", profile="test", configured="continue_anyway",
)
self.assertEqual(result["failure_policy"], "halt")
self.assertFalse(result["valid"])
class TaxonomyTests(unittest.TestCase):
def test_same_h_label_cannot_be_interpreted_as_same_category(self) -> None:
self.assertTrue(same_legacy_label(RuntimeControl.H4, ReadinessCheck.H4))
self.assertNotEqual(RuntimeControl.H4.value, ReadinessCheck.H4.value)
self.assertNotEqual(ReportDimension.H4.value, CertificationClaim.H4.value)
def test_unsupported_runtime_intervention_is_explicit(self) -> None:
result = negotiate_intervention("i-1", "roll_back", "operator", RuntimeCapabilities())
self.assertEqual(result["status"], "unsupported")
self.assertEqual(result["reason_code"], "runtime_intervention_unsupported")
class H2DependencyTests(unittest.TestCase):
def context(self, mode: str) -> H2GateContext:
return H2GateContext(mode, "alice", "write_file", "Edit", "run-1", "test.h2", "idem", 0.5)
def script(self, directory: str, body: str) -> str:
path = Path(directory) / "gate.sh"
path.write_text("#!/usr/bin/env bash\n" + body + "\n", encoding="utf-8")
path.chmod(0o700)
return str(path)
def test_missing_gate_denies_enforce_and_degrades_observe(self) -> None:
with tempfile.TemporaryDirectory() as temp:
missing = str(Path(temp) / "missing.sh")
enforced = evaluate_h2_gate(missing, self.context("enforce"))
observed = evaluate_h2_gate(missing, self.context("observe"))
self.assertFalse(enforced["execution_allowed"])
self.assertEqual(enforced["reason_code"], "h2_gate_unavailable")
self.assertTrue(observed["execution_allowed"])
self.assertEqual(observed["decision"], "observe_only")
self.assertFalse(observed["certifiable"])
def test_permission_denied_fails_closed(self) -> None:
with tempfile.TemporaryDirectory() as temp:
path = self.script(temp, "echo 'TOOL_APPROVED tool=x reason=test'")
os.chmod(path, 0)
result = evaluate_h2_gate(path, self.context("enforce"))
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
self.assertFalse(result["execution_allowed"])
self.assertEqual(result["reason_code"], "h2_gate_permission_denied")
def test_timeout_malformed_and_internal_exception_fail_closed(self) -> None:
with tempfile.TemporaryDirectory() as temp:
timeout = self.script(temp, "sleep 1")
timed = evaluate_h2_gate(timeout, self.context("enforce"))
malformed = self.script(temp, "echo nonsense")
malformed_result = evaluate_h2_gate(malformed, self.context("enforce"))
with patch("kernel.policy.subprocess.run", side_effect=OSError("boom")):
internal = evaluate_h2_gate(malformed, self.context("enforce"))
self.assertEqual(timed["reason_code"], "h2_gate_timeout")
self.assertEqual(malformed_result["reason_code"], "h2_gate_malformed_response")
self.assertEqual(internal["reason_code"], "h2_gate_internal_error")
self.assertTrue(all(not item["execution_allowed"] for item in (timed, malformed_result, internal)))
def test_explicit_approval_and_denial_protocols(self) -> None:
with tempfile.TemporaryDirectory() as temp:
allowed = self.script(temp, "echo 'TOOL_APPROVED tool=write_file reason=registered'")
allow_result = evaluate_h2_gate(allowed, self.context("enforce"))
denied = self.script(temp, "echo 'TOOL_DENIED tool=write_file reason=missing_agent_identity' >&2; exit 2")
deny_result = evaluate_h2_gate(denied, self.context("enforce"))
self.assertTrue(allow_result["execution_allowed"])
self.assertFalse(deny_result["execution_allowed"])
self.assertEqual(deny_result["reason_code"], "h2_denied:missing_agent_identity")
class TrustRootTests(unittest.TestCase):
def test_production_refuses_local_fallback(self) -> None:
result = evaluate_trust_capabilities({"CASAN_PROFILE": "production"})
self.assertFalse(result["ready"])
self.assertFalse(result["certifiable"])
self.assertIn("external_signing_trust_root_required", result["reason_codes"])
def test_external_configuration_is_capable_but_not_claimed_provisioned(self) -> None:
result = evaluate_trust_capabilities({
"CASAN_PROFILE": "production",
"CASAN_SIGNING_PROVIDER": "vault_kms",
"VAULT_ADDR": "https://vault.example",
"VAULT_TOKEN": "redacted-runtime-token",
"CASAN_IMMUTABLE_ANCHOR_PROVIDER": "s3_object_lock",
"CASAN_S3_BUCKET": "audit-lock",
"CASAN_S3_KMS_KEY_ID": "kms-key",
})
self.assertTrue(result["ready"])
self.assertTrue(result["certifiable"])
def test_emergency_override_never_certifies(self) -> None:
result = evaluate_trust_capabilities({
"CASAN_PROFILE": "production",
"CASAN_TRUST_EMERGENCY_OVERRIDE": "1",
})
self.assertFalse(result["certifiable"])
self.assertEqual(result["severity"], "critical")
class CrossRuntimeConformanceTests(unittest.TestCase):
def events(self, **overrides: object) -> tuple[dict[str, object], dict[str, object]]:
common: dict[str, object] = {
"run_id": "run-1", "trace_id": "run-1", "action": "deploy", "tool": "Bash",
"command": "deploy harmless docs", "actor": "alice", "mode": "enforce",
"execution_status": "failed", "assurance_status": "failed", "certification_status": "non_certified",
"certified": False, "correlation_id": "corr-1", "parent_run_id": "parent-1",
"parent_step_id": "parent-step-1", "evidence": [
{"evidence_id": "e-1", "decision": "deny", "category": "runtime_control"},
],
}
common.update(overrides)
return dict(common), dict(common, adapter_version="20.2.0")
def map_both(self, **overrides: object) -> tuple[dict[str, object], dict[str, object]]:
native_event, agentic_event = self.events(**overrides)
native = NativeHarnessAdapter().map_execution(native_event)
agentic = AgenticBridgeAdapter().map_execution(agentic_event)
return native, agentic
def test_01_same_dangerous_action_classification(self) -> None:
native, agentic = self.map_both()
self.assertEqual(validate_bundle(native), [])
self.assertEqual(validate_bundle(agentic), [])
self.assertEqual(native["run"]["risk_summary"]["action_class"], "deployment")
self.assertEqual(agentic["run"]["risk_summary"]["action_class"], "deployment")
self.assertEqual(native["run"]["risk_summary"]["effective_risk"], "high")
def test_02_same_missing_actor_denial(self) -> None:
native, agentic = self.map_both(actor="")
for bundle in (native, agentic):
self.assertEqual(bundle["run"]["risk_summary"]["decision"], "deny")
self.assertIn("actor_identity_required", bundle["run"]["risk_summary"]["reason_codes"])
def test_03_same_missing_h2_gate_denial(self) -> None:
with tempfile.TemporaryDirectory() as temp:
missing = str(Path(temp) / "missing.sh")
decisions = [
evaluate_h2_gate(missing, H2GateContext("enforce", "alice", "write_file", "Edit", "run-1", point, "idem"))
for point in ("native-harness.h2", "agentic-bridge.h2")
]
native, agentic = self.map_both(
action="write_file", tool="Edit", command="safe edit",
policy_decisions=[decisions[0]], evidence=[{"evidence_id": "e-1", "decision": "deny"}],
)
# The second adapter receives the same kernel decision with only its
# enforcement-point extension changed.
agentic_event = self.events(
action="write_file", tool="Edit", command="safe edit",
policy_decisions=[decisions[1]], evidence=[{"evidence_id": "e-1", "decision": "deny"}],
)[1]
agentic = AgenticBridgeAdapter().map_execution(agentic_event)
for bundle in (native, agentic):
decision = bundle["steps"][0]["policy_decisions"][0]
self.assertEqual(decision["decision"], "deny")
self.assertIn("h2_gate_unavailable", decision["reason_codes"])
def test_04_same_approval_requirement(self) -> None:
native, agentic = self.map_both(actor="alice")
for bundle in (native, agentic):
self.assertEqual(bundle["run"]["risk_summary"]["decision"], "require_approval")
self.assertEqual(bundle["run"]["risk_summary"]["evidence_requirement"], "required")
def test_05_same_claim_evidence_relationship(self) -> None:
native, agentic = self.map_both()
for bundle in (native, agentic):
self.assertEqual(bundle["claims"][0]["evidence_refs"], ["e-1"])
self.assertEqual(bundle["evidence"][0]["claim_refs"], [bundle["claims"][0]["claim_id"]])
def test_06_observed_only_execution_is_never_certified(self) -> None:
native, agentic = self.map_both(
mode="observe", certification_status="certified", certified=True,
)
for bundle in (native, agentic):
self.assertIn("observed_only_cannot_be_certified", validate_bundle(bundle))
def test_07_execution_and_assurance_outcomes_are_distinct(self) -> None:
native, agentic = self.map_both(
execution_status="failed", assurance_status="passed", certification_status="non_certified",
)
for bundle in (native, agentic):
outcome = bundle["run"]["outcome"]
self.assertEqual(outcome["execution_result"], "failed")
self.assertEqual(outcome["assurance_result"], "passed")
self.assertEqual(outcome["certification_result"], "non_certified")
def test_08_correlation_and_causation_are_preserved(self) -> None:
native, agentic = self.map_both()
for bundle in (native, agentic):
self.assertEqual(bundle["run"]["correlation_id"], "corr-1")
self.assertEqual(bundle["run"]["parent_run_id"], "parent-1")
self.assertEqual(bundle["steps"][0]["parent_step_id"], "parent-step-1")
self.assertEqual(bundle["trace_links"][0]["from"], "run-1")
def test_09_same_verifier_detects_evidence_tampering(self) -> None:
native, agentic = self.map_both()
for original in (native, agentic):
self.assertEqual(validate_bundle(original), [])
tampered = deepcopy(original)
tampered["evidence"][0]["artifact_ref"] = "inline:tampered"
self.assertIn("evidence.0.integrity_invalid", validate_bundle(tampered))
def test_10_runtime_details_are_namespaced_extensions(self) -> None:
native, agentic = self.map_both()
self.assertNotEqual(native["run"]["runtime"]["type"], agentic["run"]["runtime"]["type"])
self.assertIn("casan.runtime.casan-native-harness", native["extensions"])
self.assertIn("casan.runtime.agentic-bridge", agentic["extensions"])
def test_delegation_depth_is_enforced(self) -> None:
native, _ = self.map_both()
native["actors"].append({
"actor_id": "child", "actor_type": "child_agent", "issuer": "alice",
"trust_level": "verified", "authentication_evidence_ref": None,
})
native["delegations"] = [
{"delegation_id": "d1", "delegator_ref": "alice", "delegate_ref": "child", "allowed_authority": ["read"], "resource_scope": ["project"], "expiry": "2030-01-01T00:00:00Z", "maximum_depth": 3, "parent_delegation_ref": None, "revoked": False, "approval_ref": None},
{"delegation_id": "d2", "delegator_ref": "alice", "delegate_ref": "child", "allowed_authority": ["read"], "resource_scope": ["project"], "expiry": "2030-01-01T00:00:00Z", "maximum_depth": 3, "parent_delegation_ref": "d1", "revoked": False, "approval_ref": None},
{"delegation_id": "d3", "delegator_ref": "alice", "delegate_ref": "child", "allowed_authority": ["read"], "resource_scope": ["project"], "expiry": "2030-01-01T00:00:00Z", "maximum_depth": 1, "parent_delegation_ref": "d2", "revoked": False, "approval_ref": None},
]
self.assertIn("delegations.2.maximum_depth_exceeded", validate_bundle(native))
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""Focused bridge integration regressions for the Assurance Kernel upgrade."""
from __future__ import annotations
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
HARNESS_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(HARNESS_ROOT / "scripts" / "python"))
import agentic_bridge as bridge
class BridgeUpgradeIntegrationTests(unittest.TestCase):
def setUp(self) -> None:
self.temp = tempfile.TemporaryDirectory()
self.state = str(Path(self.temp.name) / "state")
self.project = str(HARNESS_ROOT.parent.parent)
self.base_env = {
"CASAN_STATE_ROOT": self.state,
"CASAN_AGENTIC_ENFORCEMENT_MODE": "enforce",
"CASAN_AGENT": "boss",
"CASAN_ACTOR": "boss",
"CASAN_PROFILE": "test",
}
def tearDown(self) -> None:
self.temp.cleanup()
def begin(self, actor: str = "boss") -> dict[str, object]:
return bridge.op_begin({
"op": "begin", "client": "codex", "project": self.project,
"session": "integration", "prompt": "edit a source file safely",
"integration_mode": "project_hook", "actor": actor,
})
def fixture_gate(self, body: str) -> str:
path = Path(self.temp.name) / "h2-gate.sh"
path.write_text("#!/usr/bin/env bash\n" + body + "\n", encoding="utf-8")
path.chmod(0o700)
return str(path)
def pre_edit(self, admission_id: str) -> dict[str, object]:
return bridge.op_pre_tool({
"op": "pre-tool", "admission_id": admission_id, "tool": "Edit",
"tool_input": "update a source file", "project": self.project,
})
def test_missing_h2_gate_denies_enforce_with_structured_evidence(self) -> None:
missing = str(Path(self.temp.name) / "missing.sh")
with patch.dict(os.environ, {**self.base_env, "CASAN_H2_GATE_PATH": missing}, clear=False):
begin = self.begin()
result = self.pre_edit(str(begin["admission_id"]))
record = bridge.load_admission(str(begin["admission_id"]))
self.assertEqual(result["decision"], "deny")
self.assertEqual(result["reason"], "h2_gate_unavailable")
evidence = next(item for item in record["evidence"] if item["kind"] == "tool-registry")
self.assertEqual(evidence["facts"]["mode"], "enforce")
self.assertEqual(evidence["facts"]["actor"], "boss")
self.assertEqual(evidence["facts"]["tool"], "Edit")
self.assertEqual(evidence["facts"]["execution_id"], begin["trace_id"])
self.assertEqual(evidence["facts"]["enforcement_path"], "agentic_bridge.pre_tool.h2_registry")
def test_missing_h2_gate_observe_allows_only_degraded_non_certifiable_execution(self) -> None:
missing = str(Path(self.temp.name) / "missing.sh")
env = {**self.base_env, "CASAN_AGENTIC_ENFORCEMENT_MODE": "observe", "CASAN_H2_GATE_PATH": missing}
with patch.dict(os.environ, env, clear=False):
begin = self.begin()
result = self.pre_edit(str(begin["admission_id"]))
finalized = bridge.op_finalize({"op": "finalize", "admission_id": begin["admission_id"], "stop_reason": "completed"})
self.assertEqual(result["decision"], "allow")
self.assertEqual(finalized["decision"], "non_certified")
self.assertEqual(finalized["certification_strength"], "observed_only")
def test_registry_defaults_on_and_development_bypass_is_visible(self) -> None:
missing = str(Path(self.temp.name) / "missing.sh")
with patch.dict(os.environ, {**self.base_env, "CASAN_H2_GATE_PATH": missing}, clear=False):
begin = self.begin()
default_result = self.pre_edit(str(begin["admission_id"]))
self.assertEqual(default_result["reason"], "h2_gate_unavailable")
with patch.dict(os.environ, {**self.base_env, "CASAN_AGENTIC_H2_REGISTRY": "0", "CASAN_H2_GATE_PATH": missing}, clear=False):
begin = self.begin()
bypass = self.pre_edit(str(begin["admission_id"]))
record = bridge.load_admission(str(begin["admission_id"]))
self.assertEqual(bypass["decision"], "allow")
self.assertTrue(any("HIGH" in warning for warning in bypass["warnings"]))
self.assertEqual(record["certification_strength"], "observed_only")
def test_benign_deploy_requires_approval_from_action_floor(self) -> None:
gate = self.fixture_gate("echo 'TOOL_APPROVED tool=deploy reason=registered'")
with patch.dict(os.environ, {**self.base_env, "CASAN_H2_GATE_PATH": gate}, clear=False):
begin = self.begin()
result = bridge.op_pre_tool({
"op": "pre-tool", "admission_id": begin["admission_id"], "tool": "Bash",
"tool_input": "deploy harmless documentation", "project": self.project,
})
self.assertEqual(result["decision"], "require_approval")
risk = result["policy_decision"]
self.assertEqual(risk["action_class"], "deployment")
self.assertEqual(risk["risk_factors"]["content_risk"], "medium")
self.assertEqual(risk["effective_risk"], "high")
def test_structured_shell_payload_classifies_the_inner_read_only_command(self) -> None:
with patch.dict(os.environ, self.base_env, clear=False):
begin = self.begin()
result = bridge.op_pre_tool({
"op": "pre-tool", "admission_id": begin["admission_id"], "tool": "Bash",
"tool_input": {"command": "ls"}, "project": self.project,
})
self.assertEqual(result["decision"], "allow")
self.assertEqual(result["policy_decision"]["action_class"], "read_only")
def test_failed_side_effect_halts_and_cannot_finalize_successfully(self) -> None:
gate = self.fixture_gate("echo 'TOOL_APPROVED tool=write_file reason=registered'")
with patch.dict(os.environ, {**self.base_env, "CASAN_H2_GATE_PATH": gate}, clear=False):
begin = self.begin()
admitted = self.pre_edit(str(begin["admission_id"]))
post = bridge.op_post_tool({
"op": "post-tool", "admission_id": begin["admission_id"], "tool": "Edit",
"status": "error", "duration_ms": 2, "result": "write failed",
})
finalized = bridge.op_finalize({
"op": "finalize", "admission_id": begin["admission_id"],
"stop_reason": "completed", "assistant_summary": "completed",
})
self.assertEqual(admitted["decision"], "allow")
self.assertEqual(post["decision"], "halt")
self.assertFalse(post["assurance_may_continue"])
self.assertEqual(finalized["decision"], "non_certified")
self.assertEqual(finalized["execution_outcome"], "failed")
self.assertEqual(finalized["assurance_outcome"], "failed")
self.assertIn("failed_tool_outcome", finalized["reason"])
kernel_files = list((Path(self.state) / "logs" / "kernel").glob("*.json"))
self.assertEqual(len(kernel_files), 1)
kernel = json.loads(kernel_files[0].read_text(encoding="utf-8"))
self.assertEqual(kernel["run"]["outcome"]["execution_result"], "failed")
self.assertEqual(kernel["run"]["outcome"]["certification_result"], "non_certified")
def test_production_without_external_trust_root_never_certifies(self) -> None:
env = {**self.base_env, "CASAN_PROFILE": "production", "CASAN_AGENTIC_ENFORCEMENT_MODE": "enforce"}
with patch.dict(os.environ, env, clear=False):
begin = self.begin()
finalized = bridge.op_finalize({"op": "finalize", "admission_id": begin["admission_id"], "stop_reason": "completed"})
self.assertEqual(finalized["decision"], "non_certified")
self.assertIn("external_signing_trust_root_required", finalized["reason"])
if __name__ == "__main__":
unittest.main(verbosity=2)
@@ -40,6 +40,8 @@ print(d.get(sys.argv[1],""))'
echo "===== C1: normal turn = one admission + one trace + one metric (single model) ====="
newstate
export CASAN_AGENTIC_ENFORCEMENT_MODE=enforce
export CASAN_AGENT=boss
export CASAN_ACTOR=boss
B=$(bridge '{"op":"begin","client":"claude-code","project":"'"$PROJ"'","session":"c1","prompt":"add a helper","integration_mode":"project_hook"}')
DEC=$(printf '%s' "$B" | field decision)
AID=$(printf '%s' "$B" | field admission_id)
@@ -76,7 +78,8 @@ B=$(bridge '{"op":"begin","client":"claude-code","project":"'"$PROJ"'","session"
AID=$(printf '%s' "$B" | field admission_id); TID=$(printf '%s' "$B" | field trace_id)
OK3=1
for tool in Bash Edit Write; do
R=$(bridge '{"op":"pre-tool","admission_id":"'"$AID"'","tool":"'"$tool"'","tool_input":"x","project":"'"$PROJ"'"}')
INPUT_VALUE="x"; [[ "$tool" == "Bash" ]] && INPUT_VALUE="ls"
R=$(bridge '{"op":"pre-tool","admission_id":"'"$AID"'","tool":"'"$tool"'","tool_input":"'"$INPUT_VALUE"'","project":"'"$PROJ"'"}')
[[ "$(printf '%s' "$R" | field decision)" == "allow" ]] || OK3=0
bridge '{"op":"post-tool","admission_id":"'"$AID"'","tool":"'"$tool"'","status":"success"}' >/dev/null
done
@@ -245,7 +248,7 @@ AID=$(printf '%s' "$B" | field admission_id)
|| fail "no-bash begin did not degrade gracefully ($B)"
R=$(bridge '{"op":"pre-tool","admission_id":"'"$AID"'","tool":"Bash","tool_input":"ls","project":"'"$PROJ"'"}')
[[ "$(printf '%s' "$R" | field decision)" == "allow" ]] \
&& pass "no-bash: side-effect tool allowed (admission gate still governs)" \
&& pass "no-bash: classified read-only shell command remains available" \
|| fail "no-bash pre-tool blocked the developer ($R)"
F=$(bridge '{"op":"finalize","admission_id":"'"$AID"'","stop_reason":"completed"}')
[[ "$(printf '%s' "$F" | field decision)" == "non_certified" ]] \
@@ -0,0 +1,148 @@
#!/usr/bin/env bash
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../scripts/bash/casan-paths.sh"
S="$CASAN_HARNESS_ROOT/scripts/bash"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
PASS=0; FAIL=0
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }
echo "===== Assurance Kernel unit + cross-runtime conformance ====="
if CASAN_STATE_ROOT="$WORK/unit-state" python3 "$SCRIPT_DIR/assurance-kernel-tests.py" >/dev/null; then
pass "kernel policy and two-adapter conformance tests"
else
fail "kernel policy/conformance tests"
fi
echo "===== Agentic bridge integration ====="
if CASAN_STATE_ROOT="$WORK/bridge-state" python3 "$SCRIPT_DIR/assurance-upgrade-integration-tests.py" >/dev/null; then
pass "agentic H2, risk floor, failure outcome, trust-root integration"
else
fail "agentic upgrade integration tests"
fi
echo "===== Sandbox fail-closed selection ====="
SBX_STATE="$WORK/sandbox-state"
SBX_OUT="$(CASAN_STATE_ROOT="$SBX_STATE" CASAN_ENFORCEMENT_MODE=enforce \
CASAN_SANDBOX_MODE=container CASAN_SANDBOX_TEST_FORCE_UNAVAILABLE=1 \
bash "$S/sandbox-run.sh" --workspace "$WORK" -- sh -c 'printf should-not-run' 2>&1)"
SBX_RC=$?
if [[ "$SBX_RC" -eq 2 && "$SBX_OUT" == *"SANDBOX_ISOLATION_REQUIRED"* ]] \
&& grep -q '"reason_code":"sandbox_isolation_backend_unavailable"' "$SBX_STATE/logs/sandbox/decisions.jsonl"; then
pass "enforce mode denies unavailable isolation backend with structured evidence"
else
fail "strict sandbox unavailable behavior rc=$SBX_RC out=$SBX_OUT"
fi
DEV_OUT="$(CASAN_STATE_ROOT="$SBX_STATE" CASAN_PROFILE=test CASAN_SANDBOX_MODE=container \
CASAN_SANDBOX_TEST_FORCE_UNAVAILABLE=1 CASAN_SANDBOX_ALLOW_STATIC_FALLBACK=1 \
bash "$S/sandbox-run.sh" --workspace "$WORK" -- sh -c 'printf dev-ok' 2>/dev/null)"
[[ "$DEV_OUT" == "dev-ok" ]] && pass "explicit test-only static fallback remains available" || fail "explicit test fallback failed"
echo "===== H5 action-class risk floor ====="
GOV_STATE="$WORK/governance-state"
mkdir -p "$GOV_STATE" "$WORK/keys"
printf 'deploy a harmless documentation-only change\n' > "$WORK/deploy.txt"
GOV_OUT="$(CASAN_STATE_ROOT="$GOV_STATE" CASAN_AUDIT_KEY_DIR="$WORK/keys" CASAN_ACTOR=alice \
bash "$S/governance-check.sh" "$WORK/deploy.txt" "$WORK/deploy.out" deploy 2>&1)"
GOV_RC=$?
TRACE_FILE="$(find "$GOV_STATE/logs/trace" -name 'governance-*.json' -print -quit 2>/dev/null)"
if [[ "$GOV_RC" -eq 2 && -n "$TRACE_FILE" ]] \
&& python3 - "$TRACE_FILE" <<'PY'
import json, sys
r=json.load(open(sys.argv[1], encoding="utf-8"))
raise SystemExit(0 if r.get("action_class") == "deployment" and r.get("effective_risk") == "high" and r.get("decision") == "denied" else 1)
PY
then
pass "benign deploy text cannot lower deployment risk or bypass approval"
else
fail "deploy risk-floor regression rc=$GOV_RC out=$GOV_OUT"
fi
MISSING_ACTOR_OUT="$(CASAN_STATE_ROOT="$WORK/missing-actor-state" CASAN_AUDIT_KEY_DIR="$WORK/missing-actor-keys" \
CASAN_APPROVAL_DECISION=approve CASAN_APPROVER=bob \
bash "$S/governance-check.sh" "$WORK/deploy.txt" "$WORK/missing-actor.out" deploy 2>&1)"
MISSING_ACTOR_RC=$?
if [[ "$MISSING_ACTOR_RC" -eq 2 && "$MISSING_ACTOR_OUT" == *"actor_identity_required"* ]]; then
pass "approval cannot replace required actor identity for a high-impact action"
else
fail "missing actor was not denied rc=$MISSING_ACTOR_RC out=$MISSING_ACTOR_OUT"
fi
echo "===== Native failed side-effect completion semantics ====="
NATIVE_STATE="$WORK/native-failure-state"
printf 'safe write request\n' > "$WORK/native-input.txt"
OBSERVE_STATE="$WORK/native-observe-state"
OBSERVE_OUT="$(CASAN_STATE_ROOT="$OBSERVE_STATE" CASAN_GOVERNANCE_ROOT="$WORK/observe-governance" \
CASAN_AUDIT_KEY_DIR="$WORK/observe-keys" CASAN_ENFORCEMENT_MODE=observe CASAN_ACTOR=alice \
bash "$S/casan-harness.sh" "$WORK/native-input.txt" "$WORK/native-observe-output.txt" agent_step 2>&1)"
OBSERVE_RC=$?
if [[ "$OBSERVE_RC" -eq 0 && "$OBSERVE_OUT" == *"assurance=degraded certification=non_certified"* ]] \
&& grep -q '"assurance_result":"degraded"' "$OBSERVE_STATE/logs/kernel/"*.json; then
pass "native observe-only success is explicitly degraded and non-certified"
else
fail "native observe-only outcome was overstated rc=$OBSERVE_RC out=$OBSERVE_OUT"
fi
NATIVE_OUT="$(CASAN_STATE_ROOT="$NATIVE_STATE" CASAN_AUDIT_KEY_DIR="$WORK/native-keys" \
CASAN_ENFORCEMENT_MODE=observe CASAN_AGENT=boss CASAN_ACTOR=boss \
bash "$S/casan-harness.sh" "$WORK/native-input.txt" "$WORK/native-output.txt" write_file -- \
bash -c 'exit 7' 2>&1)"
NATIVE_RC=$?
if [[ "$NATIVE_RC" -eq 7 && "$NATIVE_OUT" != *"CASAN_HARNESS_COMPLETE"* ]] \
&& grep -q '"status":"failed"' "$NATIVE_STATE/logs/cost/metrics.jsonl" \
&& grep -q '"execution_result":"failed"' "$NATIVE_STATE/logs/kernel/"*.json; then
pass "failed native side effect records telemetry but cannot emit successful completion"
else
fail "native failed-outcome semantics rc=$NATIVE_RC out=$NATIVE_OUT"
fi
NO_OUTPUT_STATE="$WORK/native-no-output-state"
NO_OUTPUT_OUT="$(CASAN_STATE_ROOT="$NO_OUTPUT_STATE" CASAN_GOVERNANCE_ROOT="$WORK/no-output-governance" \
CASAN_AUDIT_KEY_DIR="$WORK/no-output-keys" CASAN_ENFORCEMENT_MODE=observe CASAN_ACTOR=alice \
bash "$S/casan-harness.sh" "$WORK/native-input.txt" "$WORK/native-no-output.txt" agent_step -- \
bash -c 'true' 2>&1)"
NO_OUTPUT_RC=$?
if [[ "$NO_OUTPUT_RC" -eq 1 && "$NO_OUTPUT_OUT" != *"CASAN_HARNESS_COMPLETE"* ]] \
&& grep -q '"status":"failed"' "$NO_OUTPUT_STATE/logs/cost/metrics.jsonl" \
&& grep -q '"execution_result":"failed"' "$NO_OUTPUT_STATE/logs/kernel/"*.json; then
pass "zero-exit command missing its required output remains a failed execution"
else
fail "missing required output was presented as success rc=$NO_OUTPUT_RC out=$NO_OUTPUT_OUT"
fi
echo "===== Native H2 missing dependency ====="
NATIVE_H2_STATE="$WORK/native-h2-state"
NATIVE_H2_OUT="$(CASAN_STATE_ROOT="$NATIVE_H2_STATE" CASAN_GOVERNANCE_ROOT="$WORK/native-h2-governance" \
CASAN_AUDIT_KEY_DIR="$WORK/native-h2-keys" CASAN_ENFORCEMENT_MODE=enforce CASAN_PROFILE=test \
CASAN_ACTOR=alice CASAN_APPROVAL_DECISION=approve CASAN_APPROVER=bob \
CASAN_H2_GATE_PATH="$WORK/missing-native-h2.sh" \
bash "$S/casan-harness.sh" "$WORK/native-input.txt" "$WORK/native-h2-output.txt" write_file -- \
bash -c 'printf should-not-run' 2>&1)"
NATIVE_H2_RC=$?
if [[ "$NATIVE_H2_RC" -eq 2 ]] \
&& grep -q '"reason_code":"h2_gate_unavailable"' "$NATIVE_H2_STATE/logs/policy/h2-decisions.jsonl" \
&& grep -q '"execution_result":"failed"' "$NATIVE_H2_STATE/logs/kernel/"*.json; then
pass "native enforce mode denies a missing H2 gate and emits failed canonical evidence"
else
fail "native missing H2 gate did not fail closed rc=$NATIVE_H2_RC out=$NATIVE_H2_OUT"
fi
echo "===== Production trust-root refusal ====="
printf 'read status\n' > "$WORK/read.txt"
TRUST_OUT="$(CASAN_STATE_ROOT="$WORK/trust-state" CASAN_AUDIT_KEY_DIR="$WORK/trust-keys" \
CASAN_PROFILE=production bash "$S/governance-check.sh" "$WORK/read.txt" "$WORK/read.out" agent_step 2>&1)"
TRUST_RC=$?
if [[ "$TRUST_RC" -eq 2 && "$TRUST_OUT" == *"production_trust_root_unavailable"* ]]; then
pass "production refuses local signing and local hash-chain fallback"
else
fail "production trust-root refusal rc=$TRUST_RC out=$TRUST_OUT"
fi
echo
echo "===== ASSURANCE UPGRADE SUMMARY: PASS=$PASS FAIL=$FAIL ====="
[[ "$FAIL" -eq 0 ]]
@@ -6,6 +6,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../scripts/bash/casan-paths.sh"
SHIP="$CASAN_HARNESS_ROOT/scripts/bash/audit-ship-s3.sh"
PREFLIGHT="$CASAN_HARNESS_ROOT/scripts/bash/production-preflight.sh"
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
PASS=0; FAIL=0
pass() { echo "PASS: $1"; PASS=$((PASS+1)); }
@@ -52,5 +53,109 @@ printf 'not-a-digest\n' > "$WORK/bad-head.txt"
[[ "$(rc env PATH="$WORK/bin:$PATH" CASAN_PROFILE=prod CASAN_S3_BUCKET=casan-production-audit CASAN_S3_REGION=ap-northeast-1 CASAN_S3_KMS_KEY_ID=alias/casan-audit bash "$SHIP" "$WORK/bad-head.txt")" -ne 0 ]] \
&& pass "malformed audit head is refused" || fail "malformed audit head accepted"
echo "===== Production configuration boundary ====="
mkdir -p "$WORK/tls" "$WORK/state/logs/audit" "$WORK/output"
openssl req -x509 -newkey rsa:2048 -nodes -days 60 \
-subj '/CN=control.casan.internal' -addext 'subjectAltName=DNS:control.casan.internal' \
-keyout "$WORK/tls/tls.key" -out "$WORK/tls/tls.crt" >/dev/null 2>&1
openssl genrsa -out "$WORK/idp-private.pem" 2048 >/dev/null 2>&1
openssl rsa -in "$WORK/idp-private.pem" -pubout -out "$WORK/idp-public.pem" >/dev/null 2>&1
printf 'test-ca\n' > "$WORK/vault-ca.pem"
printf '%064d\n' 0 > "$WORK/state/logs/audit/audit-head.txt"
cat > "$WORK/oauth.env" <<'EOF'
OAUTH2_PROXY_PROVIDER=oidc
OAUTH2_PROXY_OIDC_ISSUER_URL=https://id.casan.internal/realms/casan
OAUTH2_PROXY_CLIENT_ID=casan-control-plane
OAUTH2_PROXY_CLIENT_SECRET=secret-manager-injected
OAUTH2_PROXY_COOKIE_SECRET=base64-cookie-secret-value
OAUTH2_PROXY_COOKIE_SECURE=true
OAUTH2_PROXY_REDIRECT_URL=https://control.casan.internal/oauth2/callback
OAUTH2_PROXY_OIDC_GROUPS_CLAIM=groups
OAUTH2_PROXY_SET_XAUTHREQUEST=true
OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
EOF
cat > "$WORK/vault.env" <<EOF
VAULT_ADDR=https://vault.casan.internal
VAULT_TOKEN=short-lived-workload-token
VAULT_CACERT=$WORK/vault-ca.pem
EOF
write_runtime() {
local audience="$1" signing="$2" anchor="$3"
cat > "$WORK/runtime.env" <<EOF
CASAN_PROFILE=prod
CASAN_CP_AUTH_MODE=jwt
CASAN_CP_JWT_ISSUER=https://id.casan.internal/realms/casan
CASAN_CP_JWT_AUDIENCE=$audience
CASAN_CP_JWT_PUBLIC_KEY_FILE=/run/casan-idp/idp-public.pem
CASAN_CP_JWT_CLOCK_SKEW_SECONDS=60
CASAN_SIGNING_PROVIDER=$signing
CASAN_IMMUTABLE_ANCHOR_PROVIDER=$anchor
EOF
}
write_prod_env() {
local public_key="$1"
cat > "$WORK/casan-prod.env" <<EOF
CASAN_PUBLIC_FQDN=control.casan.internal
CASAN_CP_HTTPS_PORT=443
CASAN_CP_TLS_DIR=$WORK/tls
CASAN_CP_OAUTH_ENV=$WORK/oauth.env
CASAN_CP_RUNTIME_ENV=$WORK/runtime.env
CASAN_CP_VAULT_ENV=$WORK/vault.env
CASAN_CP_STATE_DIR=$WORK/state
CASAN_CP_OUTPUT_DIR=$WORK/output
CASAN_CP_IDP_PUBLIC_KEY=$public_key
CASAN_CP_API_IMAGE=registry.casan.internal/api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
CASAN_CP_UI_IMAGE=registry.casan.internal/ui@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
CASAN_CP_OAUTH2_PROXY_IMAGE=registry.casan.internal/oauth@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
CASAN_S3_BUCKET=casan-production-audit
CASAN_S3_PREFIX=audit-anchors
CASAN_S3_REGION=ap-northeast-1
CASAN_S3_RETENTION_DAYS=365
CASAN_S3_KMS_KEY_ID=alias/casan-audit
EOF
}
cat > "$WORK/bin/curl" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' '{"data":{"ttl":300,"policies":["casan-audit-sign"]}}'
EOF
cat > "$WORK/bin/docker" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "$WORK/bin/aws" <<'EOF'
#!/usr/bin/env bash
if [[ "$1 $2" == "s3api get-object-lock-configuration" ]]; then
printf '%s\n' '{"ObjectLockConfiguration":{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":365}}}}'
elif [[ "$1 $2" == "s3api head-object" ]]; then
exit 1
fi
exit 0
EOF
chmod +x "$WORK/bin/curl" "$WORK/bin/docker" "$WORK/bin/aws"
write_runtime casan-control-plane vault_kms s3_object_lock
write_prod_env "$WORK/idp-public.pem"
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -eq 0 ]] \
&& pass "complete JWT/Vault/Object-Lock production contract passes with local service stubs" \
|| fail "valid production configuration rejected"
write_runtime wrong-audience vault_kms s3_object_lock
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -ne 0 ]] \
&& pass "OIDC audience mismatch is refused" || fail "OIDC audience mismatch accepted"
write_runtime casan-control-plane local_openssl local_hash_chain
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -ne 0 ]] \
&& pass "production local signing and local ledger configuration is refused" \
|| fail "production local trust fallback accepted"
write_runtime casan-control-plane vault_kms s3_object_lock
write_prod_env "$WORK/missing-idp-public.pem"
[[ "$(rc env PATH="$WORK/bin:$PATH" bash "$PREFLIGHT" "$WORK/casan-prod.env")" -ne 0 ]] \
&& pass "missing IdP verification key is refused" || fail "missing IdP key accepted"
echo "===== PRODUCTION HANDOFF SUMMARY: PASS=$PASS FAIL=$FAIL ====="
[[ "$FAIL" -eq 0 ]]
@@ -117,7 +117,7 @@ set -e
# H5: high-risk action approved with explicit approver
APPROVED_OUT="$EVIDENCE_DIR/04-high-risk-approved-output.txt"
CASAN_APPROVAL_DECISION=approve CASAN_APPROVER=architect@example.local \
CASAN_ACTOR=developer CASAN_APPROVAL_DECISION=approve CASAN_APPROVER=architect@example.local \
"$SCRIPTS/governance-check.sh" "$RISK_IN" "$APPROVED_OUT" deploy > "$EVIDENCE_DIR/04-governance-approve.stdout"
assert_contains "$APPROVED_OUT" "Deploy"