AID-D-016.002: Reputation Scoring & Containment Escalation Signals

Version 1.20261015 · Actionable sub-technique

Score agent reputation continuously using signals such as unknown identity, signature failures, abnormal tool usage, and egress anomalies, then emit auditable escalation recommendations when thresholds are crossed.

Pillars: infra, app, data

Lifecycle: operation

Scope boundary

Owns deduplicated, time-windowed, auditable agent reputation scoring and escalation evidence. It stops at scoring and recommendation and does not execute quarantine, eviction, or restoration.

AID-D-016.001: AID-D-016.002 scores reputation from discovery and other detector signals; AID-D-016.001 owns the specific unknown-identity and new-edge finding. A new identity can inform reputation but must not be treated as the complete risk score.

AID-I-003: AID-D-016.002 emits reputation and containment-escalation evidence; AID-I-003 applies quarantine, throttling, or safe-mode containment. A threshold crossing is a recommendation until an authorized Isolate control enforces it.

Implementation guidance

AID-D-016.002-G001

Compute an auditable agent reputation score from structured security signals using time-window aggregation, de-duplication, and time decay; emit escalation events when thresholds are crossed.
Signed scoring contract

Consume the complete immutable signal window from the security-event fan-in point. A separately signed completeness receipt binds required sources, per-source counts/checkpoints, window, record count, and JSONL digest. A signed policy owns the allowed signal types, weights, confidence bounds, window/half-life, thresholds, and policy version. There is no default weight: an unknown event type produces an UNMAPPED_SIGNAL integrity finding and a non-scoreable ERROR, never a clean score or containment recommendation.

Production scorer with conflict-safe de-duplication
from __future__ import annotations

import math as _aidefend_math
import os as _aidefend_os

# Admission injects these required values from one signature-verified, versioned runtime profile.
def _aidefend_required(name):
    value = _aidefend_os.environ.get(name)
    if value is None or not value.strip():
        raise RuntimeError(f"required runtime-profile field is absent: {name}")
    return value.strip()


def _aidefend_positive_float(name):
    try:
        value = float(_aidefend_required(name))
    except ValueError as error:
        raise RuntimeError(f"runtime-profile field is not numeric: {name}") from error
    if not _aidefend_math.isfinite(value) or value <= 0:
        raise RuntimeError(f"runtime-profile field must be finite and positive: {name}")
    return value


def _aidefend_positive_int(name):
    try:
        value = int(_aidefend_required(name))
    except ValueError as error:
        raise RuntimeError(f"runtime-profile field is not an integer: {name}") from error
    if value < 1:
        raise RuntimeError(f"runtime-profile field must be positive: {name}")
    return value


def _aidefend_nonnegative_int(name):
    try:
        value = int(_aidefend_required(name))
    except ValueError as error:
        raise RuntimeError(f"runtime-profile field is not an integer: {name}") from error
    if value < 0:
        raise RuntimeError(f"runtime-profile field cannot be negative: {name}")
    return value


def _aidefend_fraction(name):
    value = _aidefend_positive_float(name)
    if value > 1:
        raise RuntimeError(f"runtime-profile field must be in (0, 1]: {name}")
    return value


RUNTIME_PROFILE_VERSION = _aidefend_required("AIDEFEND_RUNTIME_PROFILE_VERSION")
RUNTIME_PROFILE_SHA256 = _aidefend_required("AIDEFEND_RUNTIME_PROFILE_SHA256").lower()
if (len(RUNTIME_PROFILE_SHA256) != 64
        or set(RUNTIME_PROFILE_SHA256) - set("0123456789abcdef")):
    raise RuntimeError("runtime-profile digest must be lowercase SHA-256")
REPUTATION_ANALYSIS_COMMAND_TIMEOUT_SECONDS = _aidefend_positive_float("D016002_REPUTATION_COMMAND_TIMEOUT_SECONDS")

# file: detection/score_agent_reputation.py

import hashlib, hmac, json, math, os, re, subprocess, tempfile
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path

SIGNALS = Path("artifacts/reputation/security-signals.jsonl")
RECEIPT = Path("artifacts/reputation/collection-receipt.json")
POLICY = Path("policy/agent-reputation-policy.json")
EVIDENCE = Path("artifacts/reputation/AID-D-016.002-evidence.json")
_VERIFIED_INPUTS: dict[Path, bytes] = {}


def file_digest(path: Path) -> str:
    payload = _VERIFIED_INPUTS.get(path)
    if payload is None:
        payload = path.read_bytes()
    return hashlib.sha256(payload).hexdigest()


def canonical_digest(value: dict) -> str:
    payload = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
    return hashlib.sha256(payload).hexdigest()


def _write_private(path: Path, payload: bytes) -> None:
    descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    with os.fdopen(descriptor, "wb") as stream:
        stream.write(payload)
        stream.flush()
        os.fsync(stream.fileno())
    os.chmod(path, 0o400)


def _strict_json(raw: bytes) -> object:
    def unique_object(pairs):
        value = {}
        for key, item in pairs:
            if key in value:
                raise ValueError("duplicate JSON key: " + key)
            value[key] = item
        return value

    return json.loads(
        raw.decode("utf-8", errors="strict"),
        object_pairs_hook=unique_object,
        parse_constant=lambda token: (_ for _ in ()).throw(
            ValueError("non-finite JSON constant: " + token)
        ),
    )


def _strict_jsonl(raw: bytes) -> list[object]:
    text = raw.decode("utf-8", errors="strict")
    lines = text.splitlines()
    if not lines or any(not line.strip() for line in lines):
        raise ValueError("signal JSONL is empty or contains a blank record")
    return [_strict_json(line.encode("utf-8")) for line in lines]


def verify(path: Path, key_env: str) -> bytes:
    signature_path = Path(str(path) + ".sig")
    if path == signature_path:
        raise RuntimeError("signed payload and signature paths collide")
    payload_raw = path.read_bytes()
    signature_raw = signature_path.read_bytes()
    key_reference = os.environ[key_env]
    remote_key = bool(re.match(
        r"^[A-Za-z][A-Za-z0-9+.-]*://", key_reference
    )) or key_reference.startswith("pkcs11:")
    key_raw = None if remote_key else (
        key_reference.encode("utf-8")
        if key_reference.startswith("-----BEGIN ")
        else Path(key_reference).read_bytes()
    )
    with tempfile.TemporaryDirectory(prefix="aidefend-d016002-") as directory:
        root = Path(directory)
        os.chmod(root, 0o700)
        payload_snapshot = root / "payload"
        signature_snapshot = root / "payload.sig"
        _write_private(payload_snapshot, payload_raw)
        _write_private(signature_snapshot, signature_raw)
        captured = [(payload_snapshot, payload_raw), (signature_snapshot, signature_raw)]
        key_argument = key_reference
        if key_raw is not None:
            key_snapshot = root / "verify-key.pub"
            _write_private(key_snapshot, key_raw)
            captured.append((key_snapshot, key_raw))
            key_argument = str(key_snapshot)
        try:
            subprocess.run(
                ["cosign", "verify-blob", "--key", key_argument,
                 "--bundle", str(signature_snapshot), str(payload_snapshot)],
                check=True, capture_output=True, text=True,
                timeout=REPUTATION_ANALYSIS_COMMAND_TIMEOUT_SECONDS,
            )
            if any(
                not hmac.compare_digest(snapshot.read_bytes(), expected)
                for snapshot, expected in captured
            ):
                raise RuntimeError("private signature-verification snapshot changed")
        finally:
            for snapshot, _ in captured:
                os.chmod(snapshot, 0o600)
    _VERIFIED_INPUTS[path] = payload_raw
    return payload_raw


def timestamp(value: object) -> datetime:
    if not isinstance(value, str):
        raise ValueError("timestamp is absent")
    parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
    if parsed.tzinfo is None:
        raise ValueError("timestamp lacks timezone")
    return parsed.astimezone(timezone.utc)


def finite(value: object, field: str) -> float:
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise ValueError(f"{field} is not numeric")
    number = float(value)
    if not math.isfinite(number):
        raise ValueError(f"{field} is not finite")
    return number


def load() -> tuple[list[dict], dict, dict, datetime, datetime]:
    signal_raw = verify(SIGNALS, "D016002_COLLECTOR_VERIFY_KEY")
    receipt_raw = verify(RECEIPT, "D016002_COMPLETENESS_VERIFY_KEY")
    policy_raw = verify(POLICY, "D016002_POLICY_VERIFY_KEY")
    receipt = _strict_json(receipt_raw)
    policy = _strict_json(policy_raw)
    receipt_fields = {"schema_version", "asset_id", "asset_version",
                      "agent_inventory_version", "expected_agent_ids",
                      "window_start", "window_end", "required_sources",
                      "source_counts", "source_checkpoints", "collection_complete",
                      "record_count", "signals_sha256"}
    if (not isinstance(receipt, dict) or set(receipt) != receipt_fields
            or receipt["schema_version"] != "aidefend.reputation-receipt.v1"
            or receipt["signals_sha256"] != file_digest(SIGNALS)
            or not all(isinstance(receipt[field], str) and receipt[field]
                       for field in ("asset_id", "asset_version",
                                     "agent_inventory_version"))
            or not isinstance(receipt["expected_agent_ids"], list)
            or any(not isinstance(value, str) or not value
                   for value in receipt["expected_agent_ids"])
            or len(receipt["expected_agent_ids"])
            != len(set(receipt["expected_agent_ids"]))
            or not isinstance(receipt["required_sources"], list)
            or not receipt["required_sources"]
            or any(not isinstance(value, str) or not value
                   for value in receipt["required_sources"])
            or len(receipt["required_sources"]) != len(set(receipt["required_sources"]))
            or not isinstance(receipt["source_counts"], dict)
            or set(receipt["source_counts"]) != set(receipt["required_sources"])
            or any(isinstance(value, bool) or not isinstance(value, int) or value < 0
                   for value in receipt["source_counts"].values())
            or not isinstance(receipt["source_checkpoints"], dict)
            or set(receipt["source_checkpoints"]) != set(receipt["required_sources"])
            or not isinstance(receipt["collection_complete"], bool)
            or isinstance(receipt["record_count"], bool)
            or not isinstance(receipt["record_count"], int) or receipt["record_count"] < 0):
        raise ValueError("completeness receipt is invalid or misbound")
    policy_fields = {"schema_version", "policy_version", "signal_weights", "confidence_min",
                     "confidence_max", "window_seconds", "half_life_seconds",
                     "investigate_threshold", "containment_review_threshold",
                     "maximum_evidence_age_seconds"}
    if not isinstance(policy, dict) or set(policy) != policy_fields or policy["schema_version"] != "aidefend.agent-reputation-policy.v1":
        raise ValueError("policy schema differs")
    if not isinstance(policy["policy_version"], str) or not policy["policy_version"]:
        raise ValueError("policy version is absent")
    if not isinstance(policy["signal_weights"], dict) or not policy["signal_weights"]:
        raise ValueError("signal weight map is empty")
    for event_type, weight in policy["signal_weights"].items():
        if not isinstance(event_type, str) or not event_type or finite(weight, event_type) <= 0:
            raise ValueError("signal weight map is invalid")
    minimum, maximum = finite(policy["confidence_min"], "confidence_min"), finite(policy["confidence_max"], "confidence_max")
    if not 0 <= minimum <= maximum <= 1:
        raise ValueError("confidence bounds are invalid")
    if (
        isinstance(policy["window_seconds"], bool)
        or not isinstance(policy["window_seconds"], int)
        or policy["window_seconds"] < 1
    ):
        raise ValueError("window_seconds must be a positive integer")
    if (
        isinstance(policy["maximum_evidence_age_seconds"], bool)
        or not isinstance(policy["maximum_evidence_age_seconds"], int)
        or policy["maximum_evidence_age_seconds"] < 1
    ):
        raise ValueError("maximum_evidence_age_seconds must be a positive integer")
    for field in ("half_life_seconds", "investigate_threshold", "containment_review_threshold"):
        if finite(policy[field], field) <= 0:
            raise ValueError(f"{field} must be positive")
    if policy["investigate_threshold"] >= policy["containment_review_threshold"]:
        raise ValueError("threshold order is invalid")
    signals = _strict_jsonl(signal_raw)
    if len(signals) != receipt["record_count"]:
        raise ValueError("signal population differs")
    event_fields = {"schema_version", "event_id", "dedupe_key", "agent_id", "event_type",
                    "observed_at", "source", "source_sequence", "source_event_id",
                    "confidence", "payload_sha256"}
    if any(not isinstance(item, dict) or set(item) != event_fields
           or item["schema_version"] != "aidefend.reputation-signal.v1" for item in signals):
        raise ValueError("signal schema differs")
    ids = [item["event_id"] for item in signals]
    if any(not isinstance(value, str) or not value for value in ids) or len(set(ids)) != len(ids):
        raise ValueError("event IDs are absent or duplicated")
    start, end = timestamp(receipt["window_start"]), timestamp(receipt["window_end"])
    if start >= end or int((end - start).total_seconds()) != int(policy["window_seconds"]):
        raise ValueError("window differs from signed policy")
    now = datetime.now(timezone.utc)
    if (
        end > now
        or (now - end).total_seconds()
        > policy["maximum_evidence_age_seconds"]
    ):
        raise ValueError("reputation window is future-dated or stale")
    for item in signals:
        for field in ("dedupe_key", "agent_id", "event_type", "source", "source_event_id", "payload_sha256"):
            if not isinstance(item[field], str) or not item[field]:
                raise ValueError(f"{field} is absent")
        if item["source"] not in receipt["required_sources"] or not start <= timestamp(item["observed_at"]) < end:
            raise ValueError("signal source or time is outside receipt")
        if item["agent_id"] not in set(receipt["expected_agent_ids"]):
            raise ValueError("signal agent is outside signed inventory")
        if (isinstance(item["source_sequence"], bool)
                or not isinstance(item["source_sequence"], int)
                or item["source_sequence"] < 0):
            raise ValueError("signal source sequence is invalid")
        if len(item["payload_sha256"]) != 64:
            raise ValueError("signal payload digest is invalid")
        confidence = finite(item["confidence"], "confidence")
        if not minimum <= confidence <= maximum:
            raise ValueError("confidence is outside signed bounds")
    source_event_ids = [
        (item["source"], item["source_event_id"])
        for item in signals
    ]
    if len(source_event_ids) != len(set(source_event_ids)):
        raise ValueError("source event identity is duplicated")
    observed_source_counts = {
        source: sum(1 for item in signals if item["source"] == source)
        for source in receipt["required_sources"]
    }
    if observed_source_counts != receipt["source_counts"]:
        raise ValueError("source population differs from completeness receipt")
    checkpoint_fields = {"first", "last", "complete"}
    for source, value in receipt["source_checkpoints"].items():
        if not isinstance(value, dict) or set(value) != checkpoint_fields or not isinstance(value["complete"], bool):
            raise ValueError("source checkpoint schema differs")
        sequences = sorted(item["source_sequence"] for item in signals if item["source"] == source)
        if sequences:
            if (isinstance(value["first"], bool) or not isinstance(value["first"], int)
                    or isinstance(value["last"], bool) or not isinstance(value["last"], int)
                    or sequences != list(range(value["first"], value["last"] + 1))):
                raise ValueError("source sequence population is incomplete")
        elif value["first"] is not None or value["last"] is not None:
            raise ValueError("empty source has non-empty checkpoint bounds")
    return signals, receipt, policy, start, end


def deduplicate(signals: list[dict]) -> tuple[list[dict], list[dict]]:
    groups: dict[str, list[dict]] = {}
    conflicts = []
    for item in signals:
        canonical_key = canonical_digest({
            "agent_id": item["agent_id"],
            "event_type": item["event_type"],
            "source_event_id": item["source_event_id"],
            "payload_sha256": item["payload_sha256"],
        })
        if item["dedupe_key"] != canonical_key:
            conflicts.append({
                "dedupe_key": item["dedupe_key"],
                "event_ids": [item["event_id"]],
                "reason": "producer key differs from canonical event identity",
            })
            continue
        groups.setdefault(canonical_key, []).append(item)
    unique = []
    material_fields = ("agent_id", "event_type", "observed_at", "source",
                       "source_sequence", "source_event_id", "confidence",
                       "payload_sha256")
    for key, group in sorted(groups.items()):
        fingerprints = {canonical_digest({field: item[field] for field in material_fields})
                        for item in group}
        if len(fingerprints) != 1:
            conflicts.append({"dedupe_key": key, "event_ids": sorted(item["event_id"] for item in group)})
            continue
        unique.append(sorted(group, key=lambda item: item["event_id"])[0])
    return unique, conflicts


def score() -> dict:
    signals, receipt, policy, start, end = load()
    if not receipt["expected_agent_ids"] and not signals:
        return {"status": "INSUFFICIENT_DATA",
                "reason": "empty collector population does not prove no-agent applicability",
                "dedupe_conflicts": [], "integrity_findings": [],
                "raw_population": 0, "deduplicated_population": 0,
                "recommendation": None, "contributions": [], "agent_scores": {}}
    if receipt["collection_complete"] is not True or any(
            value["complete"] is not True for value in receipt["source_checkpoints"].values()):
        return {"status": "INSUFFICIENT_DATA",
                "reason": "required source/checkpoint coverage is incomplete",
                "dedupe_conflicts": [], "integrity_findings": [],
                "raw_population": len(signals), "deduplicated_population": 0,
                "recommendation": None, "contributions": []}
    unique, conflicts = deduplicate(signals)
    unmapped = [{"event_id": item["event_id"], "event_type": item["event_type"],
                 "finding": "UNMAPPED_SIGNAL"} for item in unique
                if item["event_type"] not in policy["signal_weights"]]
    if conflicts or unmapped:
        return {"status": "ERROR", "reason": "dedupe conflict or unmapped signal prevents scoring",
                "dedupe_conflicts": conflicts, "integrity_findings": unmapped,
                "raw_population": len(signals), "deduplicated_population": len(unique),
                "recommendation": None, "contributions": []}
    now = end
    contributions = []
    totals = {agent_id: 0.0 for agent_id in receipt["expected_agent_ids"]}
    half_life = float(policy["half_life_seconds"])
    for item in unique:
        age = max(0.0, (now - timestamp(item["observed_at"])).total_seconds())
        contribution = float(policy["signal_weights"][item["event_type"]]) * float(item["confidence"]) * math.pow(0.5, age / half_life)
        totals[item["agent_id"]] = totals.get(item["agent_id"], 0.0) + contribution
        contributions.append({"event_id": item["event_id"], "dedupe_key": item["dedupe_key"],
                              "agent_id": item["agent_id"], "event_type": item["event_type"],
                              "age_seconds": age, "contribution": round(contribution, 6)})
    def recommendation_for(value: float) -> str:
        if value >= policy["containment_review_threshold"]:
            return "ESCALATE_CONTAINMENT_REVIEW"
        if value >= policy["investigate_threshold"]:
            return "INVESTIGATE"
        return "OBSERVE"

    agent_outcomes = {
        agent_id: {
            "score": round(value, 6),
            "recommendation": recommendation_for(value),
        }
        for agent_id, value in sorted(totals.items())
    }
    escalated = sorted(
        agent_id for agent_id, outcome in agent_outcomes.items()
        if outcome["recommendation"] != "OBSERVE"
    )
    aggregate = (
        "ESCALATE_CONTAINMENT_REVIEW"
        if any(
            outcome["recommendation"] == "ESCALATE_CONTAINMENT_REVIEW"
            for outcome in agent_outcomes.values()
        )
        else "INVESTIGATE" if escalated else "OBSERVE"
    )
    return {"status": "FAIL" if escalated else "PASS",
            "reason": "one or more agent scores crossed a review threshold" if escalated else "no signed agent score crossed a threshold",
            "recommendation": aggregate, "escalated_agent_ids": escalated,
            "agent_outcomes": agent_outcomes,
            "agent_scores": {key: value["score"] for key, value in agent_outcomes.items()},
            "contributions": contributions, "dedupe_conflicts": [], "integrity_findings": [],
            "raw_population": len(signals), "deduplicated_population": len(unique)}


def main() -> None:
    try:
        result = score()
        receipt = _strict_json(_VERIFIED_INPUTS[RECEIPT])
        policy = _strict_json(_VERIFIED_INPUTS[POLICY])
        evidence = {"schema_version": "aidefend.d016002-evidence.v1",
                    "control": "AID-D-016.002", **result,
                    "asset_id": receipt["asset_id"],
                    "asset_version": receipt["asset_version"],
                    "agent_inventory_version": receipt["agent_inventory_version"],
                    "expected_agent_population": len(receipt["expected_agent_ids"]),
                    "window_start": receipt["window_start"], "window_end": receipt["window_end"],
                    "signals_sha256": file_digest(SIGNALS), "receipt_sha256": file_digest(RECEIPT),
                    "policy_sha256": file_digest(POLICY), "policy_version": policy["policy_version"],
                    "evaluated_at": datetime.now(timezone.utc).isoformat()}
    except Exception as error:
        evidence = {"schema_version": "aidefend.d016002-evidence.v1",
                    "control": "AID-D-016.002", "status": "ERROR", "recommendation": None,
                    "reason": f"{type(error).__name__}: {error}",
                    "evaluated_at": datetime.now(timezone.utc).isoformat()}
    EVIDENCE.parent.mkdir(parents=True, exist_ok=True)
    EVIDENCE.write_text(json.dumps(evidence, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8")
    subprocess.run(["cosign", "sign-blob", "--yes", "--key", "env://D016002_EVIDENCE_SIGNING_KEY",
                    "--bundle", str(EVIDENCE) + ".sig", str(EVIDENCE)], check=True, timeout=REPUTATION_ANALYSIS_COMMAND_TIMEOUT_SECONDS)
    verify(EVIDENCE, "D016002_EVIDENCE_VERIFY_KEY")


if __name__ == "__main__":
    main()
Observation point, boundary, and findings

Security telemetry owners sign normalized source events and checkpoints; the agent-governance owner signs the complete agent inventory; detection engineering owns weights, decay, thresholds, and scoring at the security-event fan-in point. Bind each result to the exact asset/version and inventory version. Re-verify source artifacts and recompute the score locally; treat detector and collector results as authenticated inputs that still require local recomputation.

Action: At the reputation evaluator, reconcile and de-duplicate the complete signed signal window, compute the policy-bound score and recommendation, persist the signed replay bundle, and emit threshold-crossing recommendations to the containment decision point; this detector does not isolate an agent.

Open-source tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-D-016.002: Reputation Scoring & Containment Escalation Signals | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version