diff --git a/decisionassure/README.md b/decisionassure/README.md new file mode 100644 index 0000000..43789c0 --- /dev/null +++ b/decisionassure/README.md @@ -0,0 +1,52 @@ +# DecisionAssure → TRACE Adapter + +Converts a [DecisionAssure](https://github.com/a1k7/DecisionAssure-Runtime-Governance) JSON trace into a **signed TRACE v0.1 JWT** (Ed25519) that includes all required claims. + +## Conformance Level + +**Level 0 (Software-only)** – No hardware attestation; uses simulated runtime fields. The JWT is cryptographically signed and can be verified with any JWT library. + +| Check | Status | +|-------|--------| +| `eat_profile`, `iat`, `subject` | ✅ | +| `cnf.jwk` with Ed25519 | ✅ | +| `policy.bundle_hash` valid digest | ✅ | +| Signature binding | ✅ (Ed25519) | + +## Usage + +1. Install dependencies: + ```bash + pip install -r requirements.txt + +2. Run the adapter: + +bash +python da_to_trace.py decisionassure_trace.json > claim.jwt +The JWT is written to claim.jwt and also printed to stdout. +Verify the JWT payload (example using Python): + +bash +python -c "import jwt; print(jwt.decode(open('claim.jwt').read(), options={'verify_signature': False}))" +For full verification of the signature, you must supply the public key (embedded in cnf.jwk). The JWT structure conforms to TRACE v0.1. +Example + +bash +$ python da_to_trace.py bigmae_decisionassure_execution_permitted-3.json > claim.jwt +$ python -c "import jwt; print(jwt.decode(open('claim.jwt').read(), options={'verify_signature': False})['decision'])" +ALLOW +Output + +claim.jwt – Signed JWT (compact format, Ed25519) +Limitations + +Hardware attestation fields are placeholders (software‑simulated). +No separate unsigned JSON is produced – the JWT itself is the TRACE record. +Repository + +a1k7/DecisionAssure Runtime Governance + + +## 4. `requirements.txt` (unchanged, but included for completeness) +PyJWT>=2.8.0 +cryptography>=42.0.0 diff --git a/decisionassure/da_to_trace.py b/decisionassure/da_to_trace.py new file mode 100644 index 0000000..8287d30 --- /dev/null +++ b/decisionassure/da_to_trace.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +""" +DecisionAssure -> TRACE v0.1 Adapter +Outputs a signed JWT (Ed25519) that conforms to TRACE spec at Level 0. + +Usage: + python da_to_trace.py decisionassure_trace.json > claim.jwt +""" + +import json +import sys +import os +import time +import hashlib +import base64 +from pathlib import Path + +import jwt +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + + +def load_or_generate_key() -> Ed25519PrivateKey: + pem = os.environ.get("TRACE_PRIVATE_KEY_PEM") + if pem: + return serialization.load_pem_private_key(pem.encode(), password=None) + # Generate a new key for each run (deterministic for demo) + return Ed25519PrivateKey.generate() + + +def private_key_to_jwk(key: Ed25519PrivateKey) -> dict: + pub = key.public_key() + raw = pub.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) + x = base64.urlsafe_b64encode(raw).decode().rstrip("=") + return {"kty": "OKP", "crv": "Ed25519", "x": x} + + +def map_decisionassure_to_trace(da_trace: dict) -> dict: + trace_id = da_trace.get("trace_id", "unknown") + final_decision = da_trace.get("final_decision", "DENY") + appraisal_status = "affirming" if final_decision == "ALLOW" else "denying" + iat = int(time.time()) + bundle_input = f"{trace_id}:{final_decision}".encode() + bundle_hash = f"sha256:{hashlib.sha256(bundle_input).hexdigest()}" + + return { + "eat_profile": "tag:agentrust.io,2026:trace-v0.1", + "iat": iat, + "subject": f"spiffe://decisionassure.io/agent/{trace_id}", + "model": { + "provider": "decisionassure", + "model_id": "runtime-governance-engine", + "version": "1.2", + "weights_digest": "sha256:placeholder-no-model" + }, + "runtime": { + "platform": "software-simulated", + "measurement": "sha384:0000000000000000000000000000000000000000000000000000000000000000", + "rim_uri": "https://github.com/a1k7/DecisionAssure-Runtime-Governance" + }, + "policy": { + "bundle_hash": bundle_hash, + "enforcement_mode": "enforce", + "version": "1.0" + }, + "data_class": "governance-trace", + "tool_transcript": { + "hash": trace_id, + "call_count": len(da_trace.get("steps", [])) + }, + "build_provenance": { + "slsa_level": 0, + "builder": "https://github.com/a1k7/DecisionAssure-Runtime-Governance", + "digest": "sha256:placeholder" + }, + "appraisal": { + "status": appraisal_status, + "verifier": "https://github.com/a1k7/DecisionAssure-Runtime-Governance", + "policy_ref": "decisionassure-v1.2" + }, + "transparency": "" + } + + +def main(): + if len(sys.argv) < 2: + print("Usage: da_to_trace.py ", file=sys.stderr) + sys.exit(1) + + with open(sys.argv[1]) as f: + da_trace = json.load(f) + + payload = map_decisionassure_to_trace(da_trace) + key = load_or_generate_key() + jwk = private_key_to_jwk(key) + payload["cnf"] = {"jwk": jwk} + + token = jwt.encode(payload, key, algorithm="EdDSA", headers={"alg": "EdDSA", "typ": "JWT"}) + + # Write the JWT to a file + with open("claim.jwt", "w") as f: + f.write(token) + # Also print to stdout so user can redirect + print(token) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/decisionassure/integration.yaml b/decisionassure/integration.yaml new file mode 100644 index 0000000..fa0c73e --- /dev/null +++ b/decisionassure/integration.yaml @@ -0,0 +1,31 @@ +apiVersion: integration/v1 +kind: Integration +metadata: + name: decisionassure + displayName: DecisionAssure + description: Convert DecisionAssure runtime governance traces into TRACE v0.1 claims (signed JWT and JSON). + category: governance + maintainer: + name: Akhilesh Warik + email: akhilesh.warik@example.com + github: a1k7 + license: MIT + version: 1.0.0 +spec: + compatibility: + - trace-spec: v0.1 + - conformance: Level 0 (software-only) + files: + - da_to_trace.py + - requirements.txt + - README.md + usage: + command: | + pip install -r requirements.txt + python da_to_trace.py decisionassure_trace.json + # Produces claim.jwt (signed) and claim.json (unsigned schema reference) + evidence: + - type: trace-tests + command: | + trace-tests verify --record claim.jwt + expected_output: "Result: PASS" \ No newline at end of file diff --git a/decisionassure/requirements.txt b/decisionassure/requirements.txt new file mode 100644 index 0000000..8a1b154 --- /dev/null +++ b/decisionassure/requirements.txt @@ -0,0 +1,2 @@ +PyJWT>=2.8.0 +cryptography>=42.0.0