chore: pre-migration snapshot
Some checks failed
WAF Intelligence Guardrail / waf-intel (push) Waiting to run
Cloudflare Registry Validation / validate-registry (push) Has been cancelled

Layer0, MCP servers, Terraform consolidation
This commit is contained in:
Vault Sovereign
2025-12-27 01:52:27 +00:00
parent 7f2e60e1c5
commit f0b8d962de
67 changed files with 14887 additions and 650 deletions

392
layer0/learn.py Normal file
View File

@@ -0,0 +1,392 @@
from __future__ import annotations
import argparse
import hashlib
import json
import os
import sqlite3
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable
from .pattern_store import (
normalize_query_for_matching,
pattern_dict,
write_pattern_snapshot,
)
THIS_FILE = Path(__file__).resolve()
LAYER0_DIR = THIS_FILE.parent
REPO_ROOT = LAYER0_DIR.parent.parent
def _utc_now_iso_z() -> str:
return (
datetime.now(timezone.utc)
.replace(microsecond=0)
.isoformat()
.replace("+00:00", "Z")
)
def _default_db_path() -> Path:
for key in ("LEDGER_DB_PATH", "VAULTMESH_LEDGER_DB"):
v = (os.environ.get(key) or "").strip()
if v:
return Path(v).expanduser().resolve()
return (REPO_ROOT / ".state" / "ledger.sqlite").resolve()
def _default_candidate_path() -> Path:
return (REPO_ROOT / ".state" / "layer0_patterns_candidate.json").resolve()
def _read_jsonl(paths: Iterable[Path]) -> list[dict[str, Any]]:
events: list[dict[str, Any]] = []
for path in paths:
if not path.exists():
continue
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
if isinstance(obj, dict):
events.append(obj)
return events
def _telemetry_actor(event: dict[str, Any]) -> str | None:
v = event.get("actor") or event.get("user") or event.get("account")
if isinstance(v, str) and v.strip():
return v.strip()
meta = event.get("metadata")
if isinstance(meta, dict):
v2 = meta.get("actor") or meta.get("account")
if isinstance(v2, str) and v2.strip():
return v2.strip()
return None
def _telemetry_trace_id(event: dict[str, Any]) -> str | None:
for k in ("trace_id", "layer0_trace_id", "trace", "id"):
v = event.get(k)
if isinstance(v, str) and v.strip():
return v.strip()
return None
def _telemetry_ts(event: dict[str, Any]) -> str | None:
for k in ("timestamp", "ts", "time"):
v = event.get(k)
if isinstance(v, str) and v.strip():
return v.strip()
return None
def _telemetry_query(event: dict[str, Any]) -> str:
v = event.get("query") or event.get("prompt") or event.get("input")
if isinstance(v, str):
return v
meta = event.get("metadata")
if isinstance(meta, dict) and isinstance(meta.get("query"), str):
return str(meta.get("query"))
return ""
def _outcome(event: dict[str, Any]) -> str | None:
v = event.get("outcome") or event.get("result") or event.get("status")
if isinstance(v, str) and v.strip():
return v.strip()
return None
def _layer0_classification(event: dict[str, Any]) -> str | None:
v = event.get("layer0_classification") or event.get("classification")
if isinstance(v, str) and v.strip():
return v.strip()
return None
def _infer_target_from_event(
event: dict[str, Any], *, include_relaxations: bool
) -> tuple[str, str] | None:
"""
Returns (mode, classification) or None.
mode:
- "escalate": adds/strengthens detection immediately
- "relax": can reduce severity only after replay + explicit approval
"""
outcome = (_outcome(event) or "").lower()
l0 = (_layer0_classification(event) or "").lower()
# Ground-truth blocked downstream: L0 should tighten.
if outcome in {
"blocked_by_guardrails",
"blocked_by_policy",
"blocked",
"denied",
} and l0 in {"blessed", "ambiguous"}:
return ("escalate", "forbidden")
if (
outcome in {"fail_closed", "catastrophic", "blocked_catastrophic"}
and l0 != "catastrophic"
):
return ("escalate", "catastrophic")
# Preboot logs (already blocked) can still be used to learn more specific signatures.
if not outcome and l0 in {"forbidden", "catastrophic"}:
return ("escalate", l0)
# False positives: relax only after replay + approval.
if include_relaxations and outcome in {"success", "ok"} and l0 in {"forbidden"}:
return ("relax", "blessed")
return None
def _default_risk_score(classification: str) -> int:
if classification == "catastrophic":
return 5
if classification == "forbidden":
return 3
if classification == "ambiguous":
return 1
return 0
@dataclass
class _Bucket:
traces: set[str]
actors: set[str]
last_seen: str | None
def _ensure_ledger_schema(conn: sqlite3.Connection) -> None:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS proof_artifacts (
id TEXT PRIMARY KEY,
ts TEXT NOT NULL DEFAULT (datetime('now')),
kind TEXT NOT NULL,
path TEXT,
sha256_hex TEXT,
blake3_hex TEXT,
size_bytes INTEGER,
meta_json TEXT,
trace_id TEXT
);
"""
)
def _log_artifact(
*,
kind: str,
path: Path | None,
meta: dict[str, Any],
trace_id: str | None,
db_path: Path,
) -> str:
try:
from ledger.db import log_proof_artifact # type: ignore
return log_proof_artifact(
kind=kind,
path=path,
meta=meta,
trace_id=trace_id,
db_path=db_path,
)
except Exception:
pass
artifact_id = str(uuid.uuid4())
rel_path: str | None = None
sha256_hex: str | None = None
size_bytes: int | None = None
if path is not None:
try:
rel_path = str(path.resolve().relative_to(REPO_ROOT))
except Exception:
rel_path = str(path)
if path.exists() and path.is_file():
data = path.read_bytes()
sha256_hex = hashlib.sha256(data).hexdigest()
size_bytes = len(data)
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(db_path), isolation_level=None)
try:
_ensure_ledger_schema(conn)
conn.execute(
"""
INSERT INTO proof_artifacts (
id, ts, kind, path, sha256_hex, blake3_hex, size_bytes, meta_json, trace_id
)
VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?);
""",
(
artifact_id,
_utc_now_iso_z(),
kind,
rel_path,
sha256_hex,
size_bytes,
json.dumps(meta, ensure_ascii=False, sort_keys=True),
trace_id,
),
)
finally:
conn.close()
return artifact_id
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Layer0: build candidate patterns from telemetry."
)
parser.add_argument(
"--telemetry-jsonl",
action="append",
default=[],
help="Path to telemetry JSONL (repeatable). Defaults include anomalies/preboot_shield.jsonl if present.",
)
parser.add_argument("--min-support", type=int, default=3)
parser.add_argument("--min-actors", type=int, default=2)
parser.add_argument("--max-tokens", type=int, default=8)
parser.add_argument(
"--include-relaxations",
action="store_true",
help="Generate relaxation candidates (still requires replay + explicit promotion).",
)
parser.add_argument("--out", type=str, default=str(_default_candidate_path()))
parser.add_argument("--db", type=str, default=None)
args = parser.parse_args(argv)
paths: list[Path] = []
for p in args.telemetry_jsonl:
if p:
paths.append(Path(p).expanduser())
default_preboot = REPO_ROOT / "anomalies" / "preboot_shield.jsonl"
if default_preboot.exists() and default_preboot not in paths:
paths.append(default_preboot)
events = _read_jsonl(paths)
buckets: dict[tuple[str, str, tuple[str, ...]], _Bucket] = {}
for ev in events:
inferred = _infer_target_from_event(
ev, include_relaxations=bool(args.include_relaxations)
)
if not inferred:
continue
mode, target = inferred
norm = normalize_query_for_matching(_telemetry_query(ev))
tokens = norm.split()
if len(tokens) < 2:
continue
if args.max_tokens and len(tokens) > args.max_tokens:
tokens = tokens[: int(args.max_tokens)]
key = (mode, target, tuple(tokens))
b = buckets.get(key)
if b is None:
b = _Bucket(traces=set(), actors=set(), last_seen=None)
buckets[key] = b
trace = _telemetry_trace_id(ev)
if trace:
b.traces.add(trace)
actor = _telemetry_actor(ev)
if actor:
b.actors.add(actor)
ts = _telemetry_ts(ev)
if ts and (b.last_seen is None or ts > b.last_seen):
b.last_seen = ts
patterns: list[dict[str, Any]] = []
for (mode, target, tokens), bucket in buckets.items():
support = len(bucket.traces) if bucket.traces else 0
actors = len(bucket.actors)
if support < int(args.min_support):
continue
if actors and actors < int(args.min_actors):
continue
patterns.append(
pattern_dict(
tokens_all=tokens,
classification=target,
reason="telemetry_learned",
risk_score=_default_risk_score(target),
flags=["telemetry_learned"],
min_support=support,
last_seen=bucket.last_seen,
source={"support_traces": support, "support_actors": actors},
mode=mode,
pattern_id=str(uuid.uuid4()),
)
)
# Deterministic ordering: most severe, then most specific/support.
severity_rank = {
"blessed": 0,
"ambiguous": 1,
"forbidden": 2,
"catastrophic": 3,
}
patterns.sort(
key=lambda p: (
severity_rank.get(p["classification"], 0),
int(p.get("specificity_score") or 0),
int(p.get("min_support") or 0),
str(p.get("last_seen") or ""),
),
reverse=True,
)
out_path = Path(args.out).expanduser().resolve()
write_pattern_snapshot(out_path, patterns)
db_path = Path(args.db).expanduser().resolve() if args.db else _default_db_path()
artifact_id = _log_artifact(
kind="shadow_pattern_candidate",
path=out_path,
meta={
"patterns": len(patterns),
"min_support": int(args.min_support),
"min_actors": int(args.min_actors),
"inputs": [str(p) for p in paths],
},
trace_id=None,
db_path=db_path,
)
print(f"Wrote {len(patterns)} candidate patterns to {out_path}")
print(f"Logged artifact {artifact_id} to {db_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

331
layer0/pattern_store.py Normal file
View File

@@ -0,0 +1,331 @@
from __future__ import annotations
import json
import os
import re
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable, Sequence
THIS_FILE = Path(__file__).resolve()
LAYER0_DIR = THIS_FILE.parent
REPO_ROOT = LAYER0_DIR.parent.parent
_RE_URL = re.compile(r"\bhttps?://\S+\b", re.IGNORECASE)
_RE_EMAIL = re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.IGNORECASE)
_RE_IPV4 = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
_RE_IPV6 = re.compile(r"\b(?:[0-9a-f]{0,4}:){2,}[0-9a-f]{0,4}\b", re.IGNORECASE)
_RE_UUID = re.compile(
r"\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b",
re.IGNORECASE,
)
_RE_HEX_LONG = re.compile(r"\b[0-9a-f]{32,}\b", re.IGNORECASE)
_RE_BASE64ISH = re.compile(r"\b[A-Za-z0-9+/]{28,}={0,2}\b")
_RE_PATHISH = re.compile(r"(?:(?:\.\.?/)|/)[A-Za-z0-9._~/-]{2,}")
_RE_NUMBER = re.compile(r"\b\d+\b")
_RE_TOKEN = re.compile(r"[a-z][a-z_-]{1,31}", re.IGNORECASE)
SAFE_VOCAB = {
# Governance / safety verbs
"disable",
"override",
"bypass",
"skip",
"ignore",
"evade",
"break",
"force",
"apply",
"deploy",
"destroy",
"delete",
"drop",
"remove",
"exfiltrate",
# Critical nouns / domains
"guardrails",
"permissions",
"governance",
"git",
"gitops",
"dashboard",
"manual",
"prod",
"production",
"staging",
"terraform",
"waf",
"dns",
"tunnel",
"access",
"token",
"secret",
"key",
"credential",
"admin",
"root",
# Phrases often seen in L0 rules (tokenized)
"self",
"modifying",
"directly",
}
def _utc_now_iso_z() -> str:
return (
datetime.now(timezone.utc)
.replace(microsecond=0)
.isoformat()
.replace("+00:00", "Z")
)
def normalize_query_for_matching(query: str) -> str:
"""
Produce a low-leakage normalized string suitable for storing and matching.
Invariants:
- Never stores raw URLs, IPs, emails, long hex strings, base64ish blobs, UUIDs, or paths.
- Numbers are stripped to <NUM>.
- Only safe vocabulary tokens are preserved; other words are dropped.
"""
q = (query or "").lower().strip()
if not q:
return ""
# Keep placeholders lowercase to make matching stable across sources.
q = _RE_URL.sub("<url>", q)
q = _RE_EMAIL.sub("<email>", q)
q = _RE_IPV4.sub("<ip>", q)
q = _RE_IPV6.sub("<ip>", q)
q = _RE_UUID.sub("<uuid>", q)
q = _RE_PATHISH.sub("<path>", q)
q = _RE_HEX_LONG.sub("<hex>", q)
q = _RE_BASE64ISH.sub("<b64>", q)
q = _RE_NUMBER.sub("<num>", q)
# Tokenize; keep placeholders and a tight safe vocabulary.
tokens: list[str] = []
for raw in re.split(r"[^a-z0-9_<>\-_/]+", q):
t = raw.strip()
if not t:
continue
if t.startswith("<") and t.endswith(">"):
tokens.append(t)
continue
if _RE_TOKEN.fullmatch(t) and t in SAFE_VOCAB:
tokens.append(t)
# De-dupe while preserving order.
seen: set[str] = set()
out: list[str] = []
for t in tokens:
if t in seen:
continue
seen.add(t)
out.append(t)
return " ".join(out)
def normalized_tokens(query: str) -> list[str]:
s = normalize_query_for_matching(query)
return s.split() if s else []
@dataclass(frozen=True)
class LearnedPattern:
pattern_id: str
tokens_all: tuple[str, ...]
classification: str
reason: str | None
risk_score: int
flags: tuple[str, ...]
specificity_score: int
min_support: int
last_seen: str | None
source: dict[str, Any] | None
mode: str # "escalate" | "relax"
def matches(self, normalized_query: str) -> bool:
if not normalized_query:
return False
hay = set(normalized_query.split())
return all(t in hay for t in self.tokens_all)
def _default_active_path() -> Path:
configured = os.environ.get("LAYER0_ACTIVE_PATTERNS_PATH")
if configured:
return Path(configured).expanduser().resolve()
return (REPO_ROOT / ".state" / "layer0_patterns_active.json").resolve()
class PatternStore:
"""
Read-only active pattern snapshot.
This is intentionally immutable during request handling; mutations happen in
offline jobs (learn/replay) that write a new snapshot and log an artifact.
"""
def __init__(self, active_path: Path | None = None):
self._active_path = active_path or _default_active_path()
self._active: list[LearnedPattern] = []
self._loaded = False
@property
def active_path(self) -> Path:
return self._active_path
def load(self) -> None:
if self._loaded:
return
self._loaded = True
self._active = self._load_patterns_file(self._active_path)
def patterns(self) -> list[LearnedPattern]:
self.load()
return list(self._active)
def match_ordered(self, normalized_query: str) -> list[LearnedPattern]:
self.load()
matched = [p for p in self._active if p.matches(normalized_query)]
severity_rank = {
"blessed": 0,
"ambiguous": 1,
"forbidden": 2,
"catastrophic": 3,
}
matched.sort(
key=lambda p: (
severity_rank.get(p.classification, 0),
p.specificity_score,
p.min_support,
p.last_seen or "",
),
reverse=True,
)
return matched
@staticmethod
def _load_patterns_file(path: Path) -> list[LearnedPattern]:
if not path.exists():
return []
data = json.loads(path.read_text(encoding="utf-8"))
items = data.get("patterns") if isinstance(data, dict) else data
if not isinstance(items, list):
return []
patterns: list[LearnedPattern] = []
for item in items:
if not isinstance(item, dict):
continue
tokens = item.get("tokens_all") or item.get("tokens") or []
if not isinstance(tokens, list) or not tokens:
continue
tokens_norm = tuple(
t.lower() if isinstance(t, str) else ""
for t in tokens
if isinstance(t, str)
and t
and (t.startswith("<") or t.lower() in SAFE_VOCAB)
)
if not tokens_norm:
continue
classification = item.get("classification")
if classification not in {
"blessed",
"ambiguous",
"forbidden",
"catastrophic",
}:
continue
flags = item.get("flags") or []
if not isinstance(flags, list):
flags = []
mode = item.get("mode") or "escalate"
if mode not in {"escalate", "relax"}:
mode = "escalate"
min_support = int(item.get("min_support") or item.get("support") or 0)
specificity = int(item.get("specificity_score") or len(tokens_norm))
risk_score = int(item.get("risk_score") or 0)
patterns.append(
LearnedPattern(
pattern_id=str(item.get("pattern_id") or item.get("id") or ""),
tokens_all=tokens_norm,
classification=classification,
reason=item.get("reason"),
risk_score=risk_score,
flags=tuple(str(f) for f in flags if isinstance(f, str)),
specificity_score=specificity,
min_support=min_support,
last_seen=item.get("last_seen"),
source=item.get("source")
if isinstance(item.get("source"), dict)
else None,
mode=mode,
)
)
severity_rank = {
"blessed": 0,
"ambiguous": 1,
"forbidden": 2,
"catastrophic": 3,
}
patterns.sort(
key=lambda p: (
severity_rank.get(p.classification, 0),
p.specificity_score,
p.min_support,
p.last_seen or "",
),
reverse=True,
)
return patterns
def pattern_dict(
*,
tokens_all: Sequence[str],
classification: str,
reason: str | None,
risk_score: int,
flags: Sequence[str],
min_support: int,
last_seen: str | None = None,
source: dict[str, Any] | None = None,
mode: str = "escalate",
pattern_id: str | None = None,
) -> dict[str, Any]:
tokens = [t for t in tokens_all if isinstance(t, str) and t]
return {
"pattern_id": pattern_id or "",
"tokens_all": tokens,
"classification": classification,
"reason": reason,
"risk_score": int(risk_score),
"flags": list(flags),
"specificity_score": int(len(tokens)),
"min_support": int(min_support),
"last_seen": last_seen or _utc_now_iso_z(),
"source": source or {},
"mode": mode,
}
def write_pattern_snapshot(path: Path, patterns: Iterable[dict[str, Any]]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
payload = {"generated_at": _utc_now_iso_z(), "patterns": list(patterns)}
path.write_text(
json.dumps(payload, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)

View File

@@ -1,22 +1,134 @@
import datetime
import hashlib
import json
import os
import re
import sqlite3
from typing import Optional
from .shadow_classifier import ShadowEvalResult, Classification
from .pattern_store import normalize_query_for_matching
from .shadow_classifier import Classification, ShadowEvalResult
class PrebootLogger:
LOG_PATH = "anomalies/preboot_shield.jsonl"
@staticmethod
def _ledger_db_path() -> str | None:
return os.getenv("VAULTMESH_LEDGER_DB") or os.getenv("LEDGER_DB_PATH")
@staticmethod
def _normalize_for_shadow_receipt(query: str) -> str:
"""
Poison-resistant normalizer for ShadowReceipt emission.
Goals:
- Normalize casing/whitespace
- Replace common secret/identifier carriers with placeholders
- Keep output stable and compact
"""
s = (query or "").lower().strip()
s = re.sub(r"\s+", " ", s)
s = re.sub(r"\bhttps?://\S+\b", "<URL>", s)
s = re.sub(r"\b\d{1,3}(?:\.\d{1,3}){3}\b", "<IP>", s)
s = re.sub(
r"\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b",
"<HEX>",
s,
flags=re.IGNORECASE,
)
s = re.sub(r"(?:(?:\.\.?/)|/|~\/)[A-Za-z0-9._~/-]{2,}", "<PATH>", s)
s = re.sub(r"\b[0-9a-f]{16,}\b", "<HEX>", s, flags=re.IGNORECASE)
s = re.sub(r"\b\d+\b", "<N>", s)
return s.strip()
@staticmethod
def _sha256_hex(text: str) -> str:
return hashlib.sha256(text.encode("utf-8", errors="ignore")).hexdigest()
@staticmethod
def _try_emit_shadow_receipt(
*,
query: str,
classification: str,
reason: str | None,
flags: list[str],
trace_id: str | None,
) -> None:
"""
Best-effort ShadowReceipt emission into the local-first SQLite ledger.
Hard constraints:
- No dependency on vaultmesh-orgine-mobile code
- Fail silently on any error (Layer 0 must never crash)
"""
db_path = PrebootLogger._ledger_db_path()
if not db_path:
return
try:
norm = PrebootLogger._normalize_for_shadow_receipt(query)
cf_hash = PrebootLogger._sha256_hex(norm)
placeholders: list[str] = []
for p in ("<URL>", "<IP>", "<PATH>", "<HEX>", "<N>"):
if p in norm:
placeholders.append(p)
meta = {
"ts_utc": datetime.datetime.now(datetime.timezone.utc)
.replace(microsecond=0)
.isoformat()
.replace("+00:00", "Z"),
"classification": classification,
"reason": reason,
"flags": (flags or [])[:64],
"normalized_query_features": {
"placeholders": placeholders,
"length": len(norm),
},
}
conn = sqlite3.connect(db_path, timeout=0.25)
try:
conn.execute("PRAGMA foreign_keys=ON;")
conn.execute(
"""
INSERT INTO shadow_receipts (
id, horizon_id, counterfactual_hash, entropy_delta,
reason_unrealized, observer_signature, trace_id, meta_json
)
VALUES (?, ?, ?, NULL, ?, NULL, ?, ?);
""",
(
PrebootLogger._sha256_hex(
meta["ts_utc"] + "|" + (trace_id or "") + "|" + cf_hash
),
"layer0_block",
cf_hash,
"layer0_block",
trace_id,
json.dumps(meta, separators=(",", ":"), ensure_ascii=False),
),
)
conn.commit()
finally:
conn.close()
except Exception:
return
@staticmethod
def log(event: ShadowEvalResult, query: str, reason_override: Optional[str] = None):
if event.classification not in (Classification.CATASTROPHIC, Classification.FORBIDDEN):
if event.classification not in (
Classification.CATASTROPHIC,
Classification.FORBIDDEN,
):
return # Only violations get logged
record = {
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
"query": query,
# Store a normalized, low-leakage representation (never raw strings).
"query": normalize_query_for_matching(query),
"classification": event.classification.value,
"reason": reason_override or event.reason,
"trace_id": event.trace_id,
@@ -31,3 +143,11 @@ class PrebootLogger:
with open(PrebootLogger.LOG_PATH, "a", encoding="utf-8") as f:
f.write(json.dumps(record) + "\n")
PrebootLogger._try_emit_shadow_receipt(
query=query,
classification=event.classification.value,
reason=reason_override or event.reason,
flags=event.flags,
trace_id=event.trace_id,
)

443
layer0/replay.py Normal file
View File

@@ -0,0 +1,443 @@
from __future__ import annotations
import argparse
import hashlib
import json
import os
import sqlite3
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable
from .pattern_store import PatternStore, write_pattern_snapshot
from .shadow_classifier import Classification, ShadowClassifier
THIS_FILE = Path(__file__).resolve()
LAYER0_DIR = THIS_FILE.parent
REPO_ROOT = LAYER0_DIR.parent.parent
def _utc_now_iso_z() -> str:
return (
datetime.now(timezone.utc)
.replace(microsecond=0)
.isoformat()
.replace("+00:00", "Z")
)
def _default_db_path() -> Path:
for key in ("LEDGER_DB_PATH", "VAULTMESH_LEDGER_DB"):
v = (os.environ.get(key) or "").strip()
if v:
return Path(v).expanduser().resolve()
return (REPO_ROOT / ".state" / "ledger.sqlite").resolve()
def _read_jsonl(paths: Iterable[Path], *, limit: int | None) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for path in paths:
if not path.exists():
continue
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
if isinstance(obj, dict):
rows.append(obj)
if limit is not None and limit > 0 and len(rows) > limit:
return rows[-limit:]
return rows
def _telemetry_query(event: dict[str, Any]) -> str:
v = event.get("query") or event.get("prompt") or event.get("input")
return v if isinstance(v, str) else ""
def _outcome(event: dict[str, Any]) -> str | None:
v = event.get("outcome") or event.get("result") or event.get("status")
if isinstance(v, str) and v.strip():
return v.strip()
return None
def _ground_truth(event: dict[str, Any]) -> Classification | None:
outcome = (_outcome(event) or "").lower()
if outcome in {"success", "ok"}:
return Classification.BLESSED
if outcome in {"blocked_by_guardrails", "blocked_by_policy", "blocked", "denied"}:
return Classification.FORBIDDEN
if outcome in {"fail_closed", "catastrophic", "blocked_catastrophic"}:
return Classification.CATASTROPHIC
return None
def _ensure_ledger_schema(conn: sqlite3.Connection) -> None:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS proof_artifacts (
id TEXT PRIMARY KEY,
ts TEXT NOT NULL DEFAULT (datetime('now')),
kind TEXT NOT NULL,
path TEXT,
sha256_hex TEXT,
blake3_hex TEXT,
size_bytes INTEGER,
meta_json TEXT,
trace_id TEXT
);
"""
)
def _log_artifact(
*,
kind: str,
path: Path | None,
meta: dict[str, Any],
trace_id: str | None,
db_path: Path,
) -> str:
try:
from ledger.db import log_proof_artifact # type: ignore
return log_proof_artifact(
kind=kind,
path=path,
meta=meta,
trace_id=trace_id,
db_path=db_path,
)
except Exception:
pass
artifact_id = str(uuid.uuid4())
rel_path: str | None = None
sha256_hex: str | None = None
size_bytes: int | None = None
if path is not None:
try:
rel_path = str(path.resolve().relative_to(REPO_ROOT))
except Exception:
rel_path = str(path)
if path.exists() and path.is_file():
data = path.read_bytes()
sha256_hex = hashlib.sha256(data).hexdigest()
size_bytes = len(data)
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(db_path), isolation_level=None)
try:
_ensure_ledger_schema(conn)
conn.execute(
"""
INSERT INTO proof_artifacts (
id, ts, kind, path, sha256_hex, blake3_hex, size_bytes, meta_json, trace_id
)
VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?);
""",
(
artifact_id,
_utc_now_iso_z(),
kind,
rel_path,
sha256_hex,
size_bytes,
json.dumps(meta, ensure_ascii=False, sort_keys=True),
trace_id,
),
)
finally:
conn.close()
return artifact_id
def _load_patterns_file(path: Path) -> list[dict[str, Any]]:
if not path.exists():
return []
data = json.loads(path.read_text(encoding="utf-8"))
items = data.get("patterns") if isinstance(data, dict) else data
return items if isinstance(items, list) else []
def _merge_patterns(
active: list[dict[str, Any]], extra: list[dict[str, Any]]
) -> list[dict[str, Any]]:
"""
Candidate patterns win on identical (mode, tokens_all, classification).
"""
def key(p: dict[str, Any]) -> tuple[str, tuple[str, ...], str]:
mode = str(p.get("mode") or "escalate")
cls = str(p.get("classification") or "")
tokens = p.get("tokens_all") or p.get("tokens") or []
if not isinstance(tokens, list):
tokens = []
return (mode, tuple(str(t).lower() for t in tokens), cls)
merged: dict[tuple[str, tuple[str, ...], str], dict[str, Any]] = {}
for p in active:
if isinstance(p, dict):
merged[key(p)] = p
for p in extra:
if isinstance(p, dict):
merged[key(p)] = p
return list(merged.values())
@dataclass
class ReplayMetrics:
total: int
baseline_false_pos: int
baseline_false_neg: int
candidate_false_pos: int
candidate_false_neg: int
catastrophic_boundary_unchanged: bool
def _is_false_positive(pred: Classification, truth: Classification) -> bool:
return truth == Classification.BLESSED and pred in {
Classification.FORBIDDEN,
Classification.CATASTROPHIC,
}
def _is_false_negative(pred: Classification, truth: Classification) -> bool:
return truth in {
Classification.FORBIDDEN,
Classification.CATASTROPHIC,
} and pred in {
Classification.BLESSED,
Classification.AMBIGUOUS,
}
def _compute_metrics(
events: list[dict[str, Any]],
baseline: ShadowClassifier,
candidate: ShadowClassifier,
) -> ReplayMetrics:
total = 0
b_fp = b_fn = 0
c_fp = c_fn = 0
catastrophic_ok = True
for ev in events:
truth = _ground_truth(ev)
if truth is None:
continue
q = _telemetry_query(ev)
total += 1
b = baseline.classify(q).classification
c = candidate.classify(q).classification
if _is_false_positive(b, truth):
b_fp += 1
if _is_false_negative(b, truth):
b_fn += 1
if _is_false_positive(c, truth):
c_fp += 1
if _is_false_negative(c, truth):
c_fn += 1
if b == Classification.CATASTROPHIC and c != Classification.CATASTROPHIC:
catastrophic_ok = False
return ReplayMetrics(
total=total,
baseline_false_pos=b_fp,
baseline_false_neg=b_fn,
candidate_false_pos=c_fp,
candidate_false_neg=c_fn,
catastrophic_boundary_unchanged=catastrophic_ok,
)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Layer0: replay candidate patterns against recent telemetry."
)
parser.add_argument(
"--candidate",
required=True,
help="Candidate snapshot JSON (from layer0.learn).",
)
parser.add_argument(
"--telemetry-jsonl",
action="append",
default=[],
help="Path to telemetry JSONL (repeatable). Must include outcome=success|blocked_by_guardrails|... for scoring.",
)
parser.add_argument("--limit", type=int, default=2000)
parser.add_argument(
"--active",
type=str,
default=None,
help="Active patterns snapshot (defaults to .state).",
)
parser.add_argument("--db", type=str, default=None)
parser.add_argument("--report-out", type=str, default=None)
parser.add_argument(
"--promote",
action="store_true",
help="If replay passes, write active snapshot update.",
)
parser.add_argument(
"--allow-relaxations",
action="store_true",
help="Allow promotion of relaxation-mode patterns (requires replay pass).",
)
parser.add_argument("--max-fp-increase", type=int, default=0)
args = parser.parse_args(argv)
telemetry_paths = [Path(p).expanduser() for p in args.telemetry_jsonl if p]
if not telemetry_paths:
default_preboot = REPO_ROOT / "anomalies" / "preboot_shield.jsonl"
if default_preboot.exists():
telemetry_paths = [default_preboot]
events = _read_jsonl(telemetry_paths, limit=int(args.limit))
active_path = (
Path(args.active).expanduser().resolve()
if args.active
else PatternStore().active_path
)
active_patterns = _load_patterns_file(active_path)
candidate_path = Path(args.candidate).expanduser().resolve()
candidate_patterns_all = _load_patterns_file(candidate_path)
candidate_patterns = [
p
for p in candidate_patterns_all
if isinstance(p, dict)
and (args.allow_relaxations or str(p.get("mode") or "escalate") != "relax")
]
baseline_classifier = ShadowClassifier(
pattern_store=PatternStore(active_path=active_path)
)
merged = _merge_patterns(active_patterns, candidate_patterns)
merged_path = (
REPO_ROOT / ".state" / "layer0_patterns_merged_replay.json"
).resolve()
write_pattern_snapshot(merged_path, merged)
candidate_classifier = ShadowClassifier(
pattern_store=PatternStore(active_path=merged_path)
)
metrics = _compute_metrics(events, baseline_classifier, candidate_classifier)
passes = (
metrics.catastrophic_boundary_unchanged
and metrics.candidate_false_pos
<= metrics.baseline_false_pos + int(args.max_fp_increase)
and metrics.candidate_false_neg <= metrics.baseline_false_neg
)
report = {
"generated_at": _utc_now_iso_z(),
"telemetry_inputs": [str(p) for p in telemetry_paths],
"candidate_snapshot": str(candidate_path),
"active_snapshot": str(active_path),
"merged_snapshot": str(merged_path),
"allow_relaxations": bool(args.allow_relaxations),
"max_fp_increase": int(args.max_fp_increase),
"metrics": {
"total_scored": metrics.total,
"baseline_false_positives": metrics.baseline_false_pos,
"baseline_false_negatives": metrics.baseline_false_neg,
"candidate_false_positives": metrics.candidate_false_pos,
"candidate_false_negatives": metrics.candidate_false_neg,
"catastrophic_boundary_unchanged": metrics.catastrophic_boundary_unchanged,
},
"passes": passes,
"promotion": {
"requested": bool(args.promote),
"performed": False,
"active_written_to": str(active_path),
"patterns_added": len(candidate_patterns),
},
}
report_out = (
Path(args.report_out).expanduser().resolve()
if args.report_out
else (REPO_ROOT / ".state" / "layer0_shadow_replay_report.json").resolve()
)
report_out.parent.mkdir(parents=True, exist_ok=True)
report_out.write_text(
json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
db_path = Path(args.db).expanduser().resolve() if args.db else _default_db_path()
report_artifact_id = _log_artifact(
kind="shadow_replay_report",
path=report_out,
meta={
"passes": passes,
"total_scored": metrics.total,
"baseline_fp": metrics.baseline_false_pos,
"baseline_fn": metrics.baseline_false_neg,
"candidate_fp": metrics.candidate_false_pos,
"candidate_fn": metrics.candidate_false_neg,
},
trace_id=None,
db_path=db_path,
)
if args.promote and passes:
# Promotion = merged active snapshot (existing + candidates), written atomically.
tmp_path = active_path.with_suffix(active_path.suffix + ".tmp")
write_pattern_snapshot(tmp_path, merged)
tmp_path.replace(active_path)
promo_artifact_id = _log_artifact(
kind="shadow_pattern_promotion",
path=active_path,
meta={
"added": len(candidate_patterns),
"source_candidate": str(candidate_path),
"merged_snapshot": str(merged_path),
},
trace_id=None,
db_path=db_path,
)
report["promotion"]["performed"] = True
report["promotion"]["artifact_id"] = promo_artifact_id
report_out.write_text(
json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
print(f"Replay report: {report_out} (passes={passes})")
print(f"Logged artifact {report_artifact_id} to {db_path}")
if args.promote:
print(
f"Promotion {'performed' if (args.promote and passes) else 'skipped'}; active={active_path}"
)
return 0 if passes else 2
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,376 @@
#!/usr/bin/env python3
"""
Enhanced Security Classification Framework for Layer0
Provides advanced classification capabilities for Cloudflare infrastructure operations
"""
from enum import Enum
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
import re
class SecurityLevel(str, Enum):
"""Security classification levels"""
LOW_RISK = "low_risk"
MEDIUM_RISK = "medium_risk"
HIGH_RISK = "high_risk"
CRITICAL_RISK = "critical_risk"
class OperationType(str, Enum):
"""Types of infrastructure operations"""
READ_ONLY = "read_only"
CONFIGURATION_CHANGE = "configuration_change"
INFRASTRUCTURE_MODIFICATION = "infrastructure_modification"
SECURITY_MODIFICATION = "security_modification"
ACCESS_CONTROL_CHANGE = "access_control_change"
class ResourceType(str, Enum):
"""Types of Cloudflare resources"""
DNS_RECORD = "dns_record"
WAF_RULE = "waf_rule"
ACCESS_RULE = "access_rule"
TUNNEL = "tunnel"
ZONE_SETTINGS = "zone_settings"
ACCOUNT_SETTINGS = "account_settings"
@dataclass
class SecurityClassification:
"""Result of security classification"""
level: SecurityLevel
operation_type: OperationType
resource_type: ResourceType
confidence: float # 0.0 to 1.0
flags: List[str]
rationale: str
requires_approval: bool
approval_threshold: Optional[str] = None
class SecurityClassifier:
"""
Advanced security classifier for Cloudflare infrastructure operations
Provides multi-dimensional risk assessment and classification
"""
def __init__(self):
# Pattern definitions for different risk levels
self.critical_patterns = [
r"delete.*all",
r"destroy.*infrastructure",
r"disable.*waf",
r"remove.*firewall",
r"bypass.*security",
r"expose.*credentials",
r"terraform.*destroy",
r"drop.*database",
]
self.high_risk_patterns = [
r"modify.*dns",
r"change.*tunnel",
r"update.*waf",
r"create.*rule",
r"modify.*access",
r"terraform.*apply",
]
self.medium_risk_patterns = [
r"create.*record",
r"update.*settings",
r"configure.*zone",
r"modify.*page",
r"change.*cache",
]
self.low_risk_patterns = [
r"list.*records",
r"get.*status",
r"show.*config",
r"read.*logs",
r"monitor.*health",
]
# Operation type patterns
self.operation_patterns = {
OperationType.READ_ONLY: [
r"list",
r"get",
r"show",
r"read",
r"monitor",
r"status",
],
OperationType.CONFIGURATION_CHANGE: [
r"configure",
r"update.*settings",
r"change.*config",
],
OperationType.INFRASTRUCTURE_MODIFICATION: [
r"create",
r"modify",
r"update",
r"delete",
r"destroy",
],
OperationType.SECURITY_MODIFICATION: [
r"waf",
r"firewall",
r"security",
r"block",
r"allow",
],
OperationType.ACCESS_CONTROL_CHANGE: [
r"access",
r"permission",
r"role",
r"policy",
],
}
# Resource type patterns
self.resource_patterns = {
ResourceType.DNS_RECORD: [r"dns", r"record", r"domain", r"zone"],
ResourceType.WAF_RULE: [r"waf", r"firewall", r"rule", r"security"],
ResourceType.ACCESS_RULE: [r"access", r"policy", r"permission"],
ResourceType.TUNNEL: [r"tunnel", r"connector", r"proxy"],
ResourceType.ZONE_SETTINGS: [r"zone.*settings", r"domain.*config"],
ResourceType.ACCOUNT_SETTINGS: [r"account.*settings", r"billing"],
}
def classify_operation(
self, operation_description: str, context: Optional[Dict[str, Any]] = None
) -> SecurityClassification:
"""
Classify an infrastructure operation based on description and context
"""
description_lower = operation_description.lower()
# Determine security level
security_level = self._determine_security_level(description_lower)
# Determine operation type
operation_type = self._determine_operation_type(description_lower)
# Determine resource type
resource_type = self._determine_resource_type(description_lower)
# Calculate confidence
confidence = self._calculate_confidence(description_lower, security_level)
# Generate flags
flags = self._generate_flags(description_lower, security_level, context)
# Generate rationale
rationale = self._generate_rationale(
security_level, operation_type, resource_type
)
# Determine if approval is required
requires_approval = security_level in [
SecurityLevel.HIGH_RISK,
SecurityLevel.CRITICAL_RISK,
]
approval_threshold = self._determine_approval_threshold(security_level)
return SecurityClassification(
level=security_level,
operation_type=operation_type,
resource_type=resource_type,
confidence=confidence,
flags=flags,
rationale=rationale,
requires_approval=requires_approval,
approval_threshold=approval_threshold,
)
def _determine_security_level(self, description: str) -> SecurityLevel:
"""Determine the security risk level"""
for pattern in self.critical_patterns:
if re.search(pattern, description):
return SecurityLevel.CRITICAL_RISK
for pattern in self.high_risk_patterns:
if re.search(pattern, description):
return SecurityLevel.HIGH_RISK
for pattern in self.medium_risk_patterns:
if re.search(pattern, description):
return SecurityLevel.MEDIUM_RISK
for pattern in self.low_risk_patterns:
if re.search(pattern, description):
return SecurityLevel.LOW_RISK
# Default to medium risk for unknown operations
return SecurityLevel.MEDIUM_RISK
def _determine_operation_type(self, description: str) -> OperationType:
"""Determine the type of operation"""
for op_type, patterns in self.operation_patterns.items():
for pattern in patterns:
if re.search(pattern, description):
return op_type
# Default to infrastructure modification for safety
return OperationType.INFRASTRUCTURE_MODIFICATION
def _determine_resource_type(self, description: str) -> ResourceType:
"""Determine the type of resource being operated on"""
for resource_type, patterns in self.resource_patterns.items():
for pattern in patterns:
if re.search(pattern, description):
return resource_type
# Default to DNS records (most common)
return ResourceType.DNS_RECORD
def _calculate_confidence(
self, description: str, security_level: SecurityLevel
) -> float:
"""Calculate confidence score for classification"""
base_confidence = 0.7
# Increase confidence for longer, more specific descriptions
word_count = len(description.split())
if word_count > 10:
base_confidence += 0.2
elif word_count > 5:
base_confidence += 0.1
# Adjust based on security level
if security_level == SecurityLevel.CRITICAL_RISK:
base_confidence += 0.1 # Critical patterns are usually clear
return min(1.0, base_confidence)
def _generate_flags(
self,
description: str,
security_level: SecurityLevel,
context: Optional[Dict[str, Any]],
) -> List[str]:
"""Generate security flags for the operation"""
flags = []
# Basic flags based on security level
if security_level == SecurityLevel.CRITICAL_RISK:
flags.extend(
["critical_risk", "requires_emergency_approval", "multi_factor_auth"]
)
elif security_level == SecurityLevel.HIGH_RISK:
flags.extend(["high_risk", "requires_senior_approval", "audit_trail"])
elif security_level == SecurityLevel.MEDIUM_RISK:
flags.extend(["medium_risk", "requires_standard_approval"])
else:
flags.extend(["low_risk", "auto_approved"])
# Context-based flags
if context:
environment = context.get("environment", "")
if environment.lower() in ["prod", "production"]:
flags.append("production_environment")
user_role = context.get("user_role", "")
if user_role.lower() in ["admin", "root"]:
flags.append("privileged_user")
# Pattern-based flags
if re.search(r"delete|destroy|remove", description):
flags.append("destructive_operation")
if re.search(r"waf|firewall|security", description):
flags.append("security_related")
if re.search(r"dns|domain|zone", description):
flags.append("dns_related")
return flags
def _generate_rationale(
self,
security_level: SecurityLevel,
operation_type: OperationType,
resource_type: ResourceType,
) -> str:
"""Generate rationale for the classification"""
rationales = {
SecurityLevel.CRITICAL_RISK: "Critical risk operation involving infrastructure destruction or security bypass",
SecurityLevel.HIGH_RISK: "High risk operation modifying core infrastructure or security settings",
SecurityLevel.MEDIUM_RISK: "Medium risk operation involving configuration changes",
SecurityLevel.LOW_RISK: "Low risk read-only operation",
}
base_rationale = rationales.get(
security_level, "Standard infrastructure operation"
)
# Add operation-specific details
if operation_type == OperationType.INFRASTRUCTURE_MODIFICATION:
base_rationale += " with infrastructure modification capabilities"
elif operation_type == OperationType.SECURITY_MODIFICATION:
base_rationale += " affecting security controls"
# Add resource-specific details
if resource_type == ResourceType.DNS_RECORD:
base_rationale += " on DNS infrastructure"
elif resource_type == ResourceType.WAF_RULE:
base_rationale += " on WAF security rules"
return base_rationale
def _determine_approval_threshold(
self, security_level: SecurityLevel
) -> Optional[str]:
"""Determine the approval threshold required"""
thresholds = {
SecurityLevel.CRITICAL_RISK: "Emergency Change Advisory Board (ECAB)",
SecurityLevel.HIGH_RISK: "Senior Infrastructure Engineer",
SecurityLevel.MEDIUM_RISK: "Team Lead",
SecurityLevel.LOW_RISK: None,
}
return thresholds.get(security_level)
# Example usage and testing
def main():
"""Example usage of the security classifier"""
classifier = SecurityClassifier()
# Test cases
test_cases = [
"Delete all DNS records for domain example.com",
"Update WAF rule to allow traffic from China",
"Create new DNS record for subdomain",
"List all current tunnels and their status",
"Modify zone settings to enable development mode",
"Destroy all terraform infrastructure",
]
print("🔐 Security Classification Framework Test")
print("=" * 60)
for test_case in test_cases:
classification = classifier.classify_operation(test_case)
print(f"\nOperation: {test_case}")
print(f"Security Level: {classification.level.value}")
print(f"Operation Type: {classification.operation_type.value}")
print(f"Resource Type: {classification.resource_type.value}")
print(f"Confidence: {classification.confidence:.2f}")
print(f"Requires Approval: {classification.requires_approval}")
if classification.approval_threshold:
print(f"Approval Threshold: {classification.approval_threshold}")
print(f"Flags: {', '.join(classification.flags)}")
print(f"Rationale: {classification.rationale}")
if __name__ == "__main__":
main()

View File

@@ -1,6 +1,8 @@
from enum import Enum
from typing import Optional, List
import uuid
from enum import Enum
from typing import Any, List, Mapping, Optional
from .pattern_store import PatternStore, normalize_query_for_matching
class Classification(str, Enum):
@@ -39,55 +41,136 @@ class ShadowClassifier:
Minimal doctrinal classifier for Layer 0 (Shadow Eval).
"""
def classify(self, query: str) -> ShadowEvalResult:
def __init__(self, pattern_store: PatternStore | None = None):
self._patterns = pattern_store or PatternStore()
def classify(
self, query: str, *, context: Mapping[str, Any] | None = None
) -> ShadowEvalResult:
"""Return a doctrinal classification for the incoming query."""
q = query.lower().strip()
q = (query or "").lower().strip()
q_norm = normalize_query_for_matching(query or "")
# 1. Catastrophic (fail closed)
if any(x in q for x in [
"disable guardrails",
"override agent permissions",
"bypass governance",
"self-modifying",
]):
return ShadowEvalResult(
classification=Classification.CATASTROPHIC,
reason="catastrophic_indicator",
risk_score=5,
flags=["permission_override", "guardrail_disable"],
# 0. Catastrophic boundary (fail closed): never relaxed at runtime.
if any(
x in q
for x in [
"disable guardrails",
"override agent permissions",
"bypass governance",
"self-modifying",
]
):
return self._apply_context(
ShadowEvalResult(
classification=Classification.CATASTROPHIC,
reason="catastrophic_indicator",
risk_score=5,
flags=["permission_override", "guardrail_disable"],
),
context,
)
# 2. Forbidden (governance violation)
if any(x in q for x in [
"skip git",
"apply directly",
"dashboard",
"manual change",
]):
return ShadowEvalResult(
classification=Classification.FORBIDDEN,
reason="governance_violation",
risk_score=3,
flags=["gitops_bypass"],
# 1. Learned patterns (highest specificity/support first)
learned = self._patterns.match_ordered(q_norm)
if learned:
p = learned[0]
return self._apply_context(
ShadowEvalResult(
classification=Classification(p.classification),
reason=p.reason or "telemetry_learned",
risk_score=int(p.risk_score),
flags=list(p.flags) + ["telemetry_learned"],
),
context,
)
# 3. Ambiguous (needs clarification)
if any(x in q for x in [
"fix it",
"change this",
"update stuff",
]) or len(q.split()) <= 2:
return ShadowEvalResult(
classification=Classification.AMBIGUOUS,
reason="insufficient_context",
risk_score=1,
flags=["needs_clarification"],
# 2. Static patterns
# 2a. Forbidden (governance violation)
if any(
x in q
for x in [
"skip git",
"apply directly",
"dashboard",
"manual change",
]
):
return self._apply_context(
ShadowEvalResult(
classification=Classification.FORBIDDEN,
reason="governance_violation",
risk_score=3,
flags=["gitops_bypass"],
),
context,
)
# 2b. Ambiguous (needs clarification)
if (
any(
x in q
for x in [
"fix it",
"change this",
"update stuff",
]
)
or len(q.split()) <= 2
):
return self._apply_context(
ShadowEvalResult(
classification=Classification.AMBIGUOUS,
reason="insufficient_context",
risk_score=1,
flags=["needs_clarification"],
),
context,
)
# 4. Blessed (valid + lawful)
return ShadowEvalResult(
classification=Classification.BLESSED,
reason=None,
risk_score=0,
return self._apply_context(
ShadowEvalResult(
classification=Classification.BLESSED,
reason=None,
risk_score=0,
),
context,
)
@staticmethod
def _apply_context(
result: ShadowEvalResult, context: Mapping[str, Any] | None
) -> ShadowEvalResult:
if not context:
return result
env = str(context.get("environment") or "").lower()
realm = str(context.get("realm") or "").lower()
capability = str(context.get("capability") or "").lower()
role = str(context.get("actor_role") or context.get("role") or "").lower()
mult = 1.0
if env in {"prod", "production"}:
mult *= 2.0
elif env in {"staging", "stage"}:
mult *= 1.5
elif env in {"dev", "development", "test"}:
mult *= 1.0
if capability in {"destroy", "delete", "write"}:
mult *= 1.5
elif capability in {"read"}:
mult *= 1.0
if role in {"admin", "root"}:
mult *= 1.2
if realm in {"terraform", "gitops", "cloudflare"}:
mult *= 1.1
weighted = int(round(result.risk_score * mult))
result.risk_score = max(0, min(5, weighted))
return result