| import os |
| import json |
| import time |
| import uuid |
| import hashlib |
| from typing import Optional, List, Dict, Any, Tuple |
|
|
| |
| |
| |
|
|
| ENGINE_NAME = "AI_Sovereign_Sentinel_Core_v1" |
| AUTHORITY_NAME = "DataClear Sovereign Authority" |
| SOVEREIGN_VERSION = "1.2-gov-ready" |
|
|
| |
| AUDIT_LOG_FILE = "sovereign_audit_log.jsonl" |
|
|
|
|
| |
| |
| |
|
|
|
|
| class SovereignFingerprint: |
| """ |
| Minimal fingerprint generator for AI Sovereign Sentinel. |
| Creates a JSON file with engine, fingerprint and issued_at. |
| """ |
|
|
| def __init__(self, engine: str, output: str = "sovereign_fingerprint.json"): |
| self.engine = engine |
| self.output = output |
|
|
| def _build(self) -> Dict[str, Any]: |
| issued_at = int(time.time()) |
| base = f"{self.engine}|{issued_at}" |
| fp_hash = hashlib.sha256(base.encode()).hexdigest() |
| return { |
| "engine": self.engine, |
| "fingerprint": fp_hash, |
| "issued_at": issued_at, |
| "version": "1.0", |
| } |
|
|
| def export(self) -> Tuple[str, str]: |
| fp = self._build() |
| with open(self.output, "w", encoding="utf-8") as f: |
| json.dump(fp, f, indent=4) |
| return self.output, fp["fingerprint"] |
|
|
|
|
| |
| |
| |
|
|
|
|
| class SovereignValidator: |
| """ |
| Simple registry to bind engine name → fingerprint string. |
| Stored in sovereign_registry.json |
| """ |
|
|
| def __init__(self, path: str = "sovereign_registry.json"): |
| self.path = path |
| if os.path.exists(self.path): |
| with open(self.path, "r", encoding="utf-8") as f: |
| self.registry = json.load(f) |
| else: |
| self.registry = {} |
|
|
| def register(self, engine: str, fingerprint: str) -> bool: |
| self.registry[engine] = { |
| "fingerprint": fingerprint, |
| "registered_at": int(time.time()), |
| } |
| with open(self.path, "w", encoding="utf-8") as f: |
| json.dump(self.registry, f, indent=4) |
| return True |
|
|
| def validate(self, engine: str, fingerprint: str) -> bool: |
| entry = self.registry.get(engine) |
| if not entry: |
| return False |
| return entry.get("fingerprint") == fingerprint |
|
|
|
|
| |
| |
| |
|
|
|
|
| def attestation_signature(att: dict) -> str: |
| """ |
| Unified, stable signature generation for attestation objects. |
| """ |
| base = ( |
| f"{att['attestation_id']}" |
| f"|{att['issued_at']}" |
| f"|{att['fingerprint']}" |
| f"|{att['issuer']}" |
| f"|{att['version']}" |
| ) |
| return hashlib.sha256(base.encode()).hexdigest() |
|
|
|
|
| def generate_attestation(fp) -> Dict[str, Any]: |
| """ |
| Create a signed attestation for a given fingerprint (dict or string). |
| """ |
| if isinstance(fp, dict): |
| fp_value = fp.get("fingerprint", "") |
| else: |
| fp_value = fp |
|
|
| att: Dict[str, Any] = { |
| "attestation_id": str(uuid.uuid4()), |
| "issued_at": int(time.time()), |
| "fingerprint": fp_value, |
| "issuer": AUTHORITY_NAME, |
| "version": "1.0", |
| } |
|
|
| att["signature"] = attestation_signature(att) |
| return att |
|
|
|
|
| def verify_attestation(att: dict) -> bool: |
| expected = attestation_signature(att) |
| return expected == att.get("signature") |
|
|
|
|
| |
| |
| |
|
|
|
|
|
|
| def build_lineage_record( |
| engine: str, |
| fingerprint: str, |
| parent_model: Optional[str] = None, |
| model_version: str = "1.0", |
| data_tags: Optional[List[str]] = None, |
| notes: Optional[str] = None, |
| provider: Optional[str] = None, |
| model_family: Optional[str] = None, |
| ) -> Dict[str, Any]: |
| """ |
| Create a cryptographically-bound lineage record for a model. |
| |
| Hash formula (ثابت و قابل بازتولید): |
| payload = f"{engine}|{fingerprint}|{model_version}|{parent_model or ''}|{created_at}" |
| """ |
|
|
| created_at = int(time.time()) |
|
|
| core_parent = parent_model or "" |
| payload = f"{engine}|{fingerprint}|{model_version}|{core_parent}|{created_at}" |
| lineage_hash = hashlib.sha256(payload.encode()).hexdigest() |
|
|
| return { |
| "lineage_id": str(uuid.uuid4()), |
| "engine": engine, |
| "fingerprint": fingerprint, |
| "parent_model": parent_model, |
| "model_version": model_version, |
| "data_tags": data_tags or [], |
| "notes": notes, |
| "provider": provider, |
| "model_family": model_family, |
| "created_at": created_at, |
| "lineage_hash": lineage_hash, |
| } |
|
|
|
|
|
|
|
|
|
|
| |
| |
| |
|
|
|
|
| def compute_risk_profile( |
| data_tags: Optional[List[str]] = None, |
| deployment_env: Optional[str] = None, |
| ) -> Dict[str, Any]: |
| """ |
| Very simple, rule-based risk scoring so that certificate |
| carries a usable risk_level and risk_score. |
| """ |
| tags = [t.lower() for t in (data_tags or [])] |
| env = (deployment_env or "").lower().strip() |
|
|
| score = 0 |
| threat_flags: List[str] = [] |
|
|
| |
| sensitive_keywords = ["pii", "health", "healthcare", "finance", "bank", "payments"] |
| for kw in sensitive_keywords: |
| if any(kw in t for t in tags): |
| score += 25 |
| threat_flags.append(f"sensitive_data:{kw}") |
| break |
|
|
| |
| if any("internal" in t or "confidential" in t for t in tags): |
| score += 15 |
| threat_flags.append("internal_confidential") |
|
|
| |
| if env in ["prod", "production", "live"]: |
| score += 40 |
| threat_flags.append("environment:production") |
| elif env in ["staging", "preprod", "test"]: |
| score += 20 |
| threat_flags.append("environment:staging") |
| elif env: |
| score += 10 |
| threat_flags.append(f"environment:{env}") |
|
|
| |
| score = max(0, min(score, 100)) |
|
|
| if score >= 70: |
| level = "HIGH" |
| elif score >= 40: |
| level = "MEDIUM" |
| else: |
| level = "LOW" |
|
|
| return { |
| "level": level, |
| "score": score, |
| "threat_flags": threat_flags, |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| def estimate_compute_cost( |
| model_name: str, |
| tokens: int, |
| provider: Optional[str] = None, |
| ) -> Dict[str, Any]: |
| """ |
| Tiny heuristic for 'compute efficiency' the government is obsessed with. |
| We approximate per-token cost by model family. |
| """ |
|
|
| name = (model_name or "").lower() |
| provider_norm = (provider or "").lower() |
|
|
| if any(k in name for k in ["gpt-4", "claude-3-opus", "gemini-1.5-pro"]) or "frontier" in name: |
| base_cost = 3.0 |
| family = "frontier" |
| elif any(k in name for k in ["gpt-4o-mini", "gpt-3.5", "sonnet", "llama-3", "mistral"]): |
| base_cost = 1.0 |
| family = "general" |
| else: |
| base_cost = 0.2 |
| family = "narrow" |
|
|
| estimated_units = base_cost * tokens |
|
|
| if family == "narrow": |
| band = "HIGH_EFFICIENCY" |
| elif family == "general": |
| band = "BALANCED" |
| else: |
| band = "HIGH_COMPUTE" |
|
|
| return { |
| "provider": provider_norm or None, |
| "model_name": model_name, |
| "family": family, |
| "estimated_compute_units": estimated_units, |
| "efficiency_band": band, |
| "tokens": tokens, |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PolicyEngine: |
| """ |
| Simple, pluggable policy engine. |
| Provides: |
| - standard safety templates (LLM safety / restriction) |
| - rule-based enforcement |
| - support for custom organisation policies |
| """ |
|
|
| def __init__(self): |
| self.policies: Dict[str, Dict[str, Any]] = {} |
| self._load_default_policies() |
|
|
| def _load_default_policies(self): |
| self.policies["SAFE_OUTPUT_BASELINE"] = { |
| "id": "SAFE_OUTPUT_BASELINE", |
| "description": "Disallow obvious harmful, illegal or highly sensitive content.", |
| "rules": { |
| "blocked_keywords": [ |
| "terrorist", |
| "child sexual", |
| "explosive instructions", |
| "bioweapon", |
| "financial fraud", |
| ] |
| }, |
| } |
| self.policies["NO_PII_LEAK"] = { |
| "id": "NO_PII_LEAK", |
| "description": "Reduce risk of leaking obvious PII markers in logs.", |
| "rules": { |
| "blocked_markers": ["national insurance number", "passport number", "nhs number"] |
| }, |
| } |
|
|
| def add_policy(self, policy: Dict[str, Any]) -> None: |
| pid = policy.get("id") |
| if not pid: |
| raise ValueError("Policy must have an 'id'") |
| self.policies[pid] = policy |
|
|
| def list_policies(self) -> List[Dict[str, Any]]: |
| return list(self.policies.values()) |
|
|
| def evaluate_output( |
| self, |
| output_text: str, |
| active_policy_ids: Optional[List[str]] = None, |
| ) -> Dict[str, Any]: |
| text = (output_text or "").lower() |
| active_policy_ids = active_policy_ids or list(self.policies.keys()) |
|
|
| violations: List[Dict[str, Any]] = [] |
|
|
| for pid in active_policy_ids: |
| policy = self.policies.get(pid) |
| if not policy: |
| continue |
|
|
| rules = policy.get("rules", {}) |
|
|
| for kw in rules.get("blocked_keywords", []): |
| if kw in text: |
| violations.append( |
| {"policy_id": pid, "type": "blocked_keyword", "value": kw} |
| ) |
|
|
| for marker in rules.get("blocked_markers", []): |
| if marker in text: |
| violations.append( |
| {"policy_id": pid, "type": "blocked_marker", "value": marker} |
| ) |
|
|
| is_allowed = len(violations) == 0 |
| return {"is_allowed": is_allowed, "violations": violations} |
|
|
|
|
| |
| |
| |
|
|
|
|
| def generate_integrity_certificate( |
| fp, |
| att, |
| output: str = "sovereign_certificate.json", |
| parent_model: Optional[str] = None, |
| model_version: str = "1.0", |
| data_tags: Optional[List[str]] = None, |
| notes: Optional[str] = None, |
| deployment_env: Optional[str] = None, |
| provider: Optional[str] = None, |
| model_family: Optional[str] = None, |
| ) -> Dict[str, Any]: |
| """ |
| Bundle fingerprint + attestation + lineage + risk profile |
| into a single integrity certificate file. |
| """ |
| if isinstance(fp, dict): |
| fp_value = fp.get("fingerprint", "") |
| else: |
| fp_value = fp |
|
|
| lineage = build_lineage_record( |
| engine=ENGINE_NAME, |
| fingerprint=fp_value, |
| parent_model=parent_model, |
| model_version=model_version, |
| data_tags=data_tags, |
| notes=notes, |
| provider=provider, |
| model_family=model_family, |
| ) |
|
|
| risk = compute_risk_profile(data_tags=data_tags, deployment_env=deployment_env) |
|
|
| issued_at = att["issued_at"] |
|
|
| cert: Dict[str, Any] = { |
| "engine": ENGINE_NAME, |
| "sovereign_version": SOVEREIGN_VERSION, |
| "fingerprint": fp_value, |
| "issued_at": issued_at, |
| "attestation": att, |
| "lineage": lineage, |
| "risk_level": risk["level"], |
| "risk_score": risk["score"], |
| "threat_flags": risk["threat_flags"], |
| "environment": deployment_env or "unspecified", |
| "audit_id": str(uuid.uuid4()), |
| } |
|
|
| with open(output, "w", encoding="utf-8") as f: |
| json.dump(cert, f, indent=4) |
|
|
| return cert |
|
|
|
|
| |
| |
| |
|
|
|
|
| def _ensure_audit_log_dir(path: str) -> None: |
| directory = os.path.dirname(path) |
| if directory and not os.path.exists(directory): |
| os.makedirs(directory, exist_ok=True) |
|
|
|
|
| def write_audit_event( |
| event_type: str, |
| engine: str, |
| fingerprint: str, |
| parent_model: Optional[str], |
| model_version: str, |
| data_tags: Optional[List[str]], |
| risk_level: Optional[str], |
| risk_score: Optional[int], |
| deployment_env: Optional[str], |
| extra: Optional[Dict[str, Any]] = None, |
| path: str = AUDIT_LOG_FILE, |
| ) -> Dict[str, Any]: |
| """ |
| Append a single audit event as JSON line into the centralised audit journal. |
| Designed to be reusable / exportable for G-Cloud / NCSC type evidence. |
| """ |
| _ensure_audit_log_dir(path) |
|
|
| entry = { |
| "event_id": str(uuid.uuid4()), |
| "event_type": event_type, |
| "engine": engine, |
| "fingerprint": fingerprint, |
| "parent_model": parent_model, |
| "model_version": model_version, |
| "data_tags": data_tags or [], |
| "risk_level": risk_level, |
| "risk_score": risk_score, |
| "deployment_env": deployment_env, |
| "timestamp": int(time.time()), |
| "extra": extra or {}, |
| } |
|
|
| with open(path, "a", encoding="utf-8") as f: |
| f.write(json.dumps(entry) + "\n") |
|
|
| return entry |
|
|
|
|
| def export_audit_log_json(path: str = AUDIT_LOG_FILE) -> List[Dict[str, Any]]: |
| """ |
| Load the audit log as a list of JSON objects. |
| """ |
| if not os.path.exists(path): |
| return [] |
|
|
| entries: List[Dict[str, Any]] = [] |
| with open(path, "r", encoding="utf-8") as f: |
| for line in f: |
| line = line.strip() |
| if not line: |
| continue |
| try: |
| obj = json.loads(line) |
| entries.append(obj) |
| except Exception: |
| |
| continue |
| return entries |
|
|
|
|
| def export_audit_log_csv(path: str = AUDIT_LOG_FILE) -> str: |
| """ |
| Export the audit log as a simple CSV string |
| (for G-Cloud / NCSC style evidence export). |
| """ |
| entries = export_audit_log_json(path=path) |
| if not entries: |
| return "" |
|
|
| headers = list(entries[0].keys()) |
| lines: List[str] = [] |
|
|
| |
| lines.append(",".join(headers)) |
|
|
| |
| for row in entries: |
| row_values: List[str] = [] |
| for h in headers: |
| value = row.get(h, "") |
| if isinstance(value, list): |
| value = ";".join(str(v) for v in value) |
| elif isinstance(value, dict): |
| value = json.dumps(value) |
| row_values.append(str(value)) |
| lines.append(",".join(row_values)) |
|
|
| return "\n".join(lines) |
|
|
|
|
| |
| |
| |
|
|
|
|
| def log_model_execution( |
| model_name: str, |
| provider: Optional[str], |
| deployment_env: str, |
| data_tags: Optional[List[str]], |
| output_text: str, |
| tokens: int, |
| parent_model: Optional[str] = None, |
| model_version: str = "1.0", |
| active_policy_ids: Optional[List[str]] = None, |
| ) -> Dict[str, Any]: |
| """ |
| Convenience helper that: |
| - computes risk profile |
| - evaluates policy engine |
| - estimates compute efficiency |
| - writes a single unified audit event |
| """ |
| |
| risk = compute_risk_profile(data_tags=data_tags, deployment_env=deployment_env) |
|
|
| |
| pe = PolicyEngine() |
| policy_result = pe.evaluate_output(output_text, active_policy_ids=active_policy_ids) |
|
|
| |
| efficiency = estimate_compute_cost( |
| model_name=model_name, |
| tokens=tokens, |
| provider=provider, |
| ) |
|
|
| extra = { |
| "model_name": model_name, |
| "provider": provider, |
| "policy_result": policy_result, |
| "efficiency": efficiency, |
| } |
|
|
| entry = write_audit_event( |
| event_type="model_execution", |
| engine=ENGINE_NAME, |
| fingerprint=efficiency["family"], |
| parent_model=parent_model, |
| model_version=model_version, |
| data_tags=data_tags, |
| risk_level=risk["level"], |
| risk_score=risk["score"], |
| deployment_env=deployment_env, |
| extra=extra, |
| ) |
|
|
| return entry |
|
|
|
|
| |
| |
| |
|
|
|
|
| def verify_certificate(cert: dict) -> bool: |
| """ |
| Verification wrapper so that app.py can import it. |
| |
| A valid certificate requires: |
| - attestation to verify successfully |
| - fingerprint / engine to be consistent across cert + lineage + attestation |
| - lineage_hash to match a recomputed hash from core fields |
| """ |
| try: |
| |
| att = cert.get("attestation") or {} |
| if not verify_attestation(att): |
| return False |
|
|
| |
| cert_fp = cert.get("fingerprint") |
| cert_engine = cert.get("engine") |
|
|
| lineage = cert.get("lineage") or {} |
| line_fp = lineage.get("fingerprint") |
| line_engine = lineage.get("engine") |
|
|
| if not cert_fp: |
| return False |
|
|
| |
| if cert_fp != att.get("fingerprint"): |
| return False |
| if cert_fp != line_fp: |
| return False |
|
|
| |
| if cert_engine and line_engine and cert_engine != line_engine: |
| return False |
|
|
| |
| engine_val = line_engine or "" |
| fp_val = line_fp or "" |
| model_ver = (lineage.get("model_version") or "") |
| parent_val = (lineage.get("parent_model") or "") |
| created_val = lineage.get("created_at") or "" |
|
|
| payload = f"{engine_val}|{fp_val}|{model_ver}|{parent_val}|{created_val}" |
| expected_hash = hashlib.sha256(payload.encode()).hexdigest() |
|
|
| if lineage.get("lineage_hash") != expected_hash: |
| return False |
|
|
| |
| return True |
|
|
| except Exception: |
| return False |
|
|