AID-H-021.002: Runtime Integrity Enforcement (Signed Configurations)

Version 1.20261015 · Actionable sub-technique

Ensures that an AI agent, at the moment of execution, loads and operates on runtime control artifacts that are cryptographically verified to be authentic and untampered. The primary focus is signed configuration, but the same fail-closed runtime-integrity boundary also applies to startup-loaded tool manifests and other approved integrity metadata that the service depends on before it begins work. This forms a verifiable trust chain from the secure build pipeline to the running agent.

Pillars: app, infra

Lifecycle: operation

Scope boundary

Owns fail-closed verification and admission of signed startup and runtime control artifacts, configuration, tool manifests, and integrity metadata before the agent operates or reloads state. It does not semantically classify authentic content as safe.

AID-H-021.001: AID-H-021.002 enforces authenticity and approved state in the running agent; AID-H-021.001 prevents insecure configuration during development. Runtime verification remains necessary even after pre-commit policy passes.

AID-D-004.005: AID-H-021.002 rejects untrusted or integrity-failed context at the runtime boundary; AID-D-004.005 verifies turn envelopes and chains and emits tamper, replay, order, or provenance findings. The detector reports evidence, while this control owns fail-closed admission.

AID-H-021.004: AID-H-021.002 proves configuration artifacts are signed and approved; AID-H-021.004 prevents the agent identity from accessing or administering the oversight surfaces that enforce those artifacts. A valid configuration can still be dangerous if it grants self-administration of supervision.

AID-H-021.005: AID-H-021.002 verifies approved startup and runtime control artifacts; AID-H-021.005 binds a workspace trust decision to the effective manifest of execution-capable surfaces. A valid signed artifact is one input to workspace trust, not proof that every activated surface was declared and accepted.

AID-M-001.002: AID-H-021.002 verifies and admits signed startup or runtime control artifacts for an agent; AID-M-001.002 inventories the release-bound model-interface tuple and reconciles effective composition across deployed replicas. Agent configuration bytes may support both checks, but runtime admission and deployed composition reconciliation remain separate outcomes and must not be scored twice.

Implementation guidance

AID-H-021.002-G001

Cryptographically sign approved configuration files in the CI/CD pipeline.
Concept

Sign only the exact configuration bytes that passed H-021.001. Use a hardware-backed asymmetric KMS key; never import a raw long-lived private key into a general-purpose runner. This guidance produces the detached signature and build receipt. Startup verification remains a separate local claim in the next guidance.

Sign and read back in CI
set -euo pipefail
: "\${CONFIG_SIGNING_KMS_URI:?set an immutable approved KMS key URI}"
CONFIG="configs/production_agent.yaml"
SIGNATURE="$CONFIG.sig"
EVIDENCE="artifacts/config-signing-receipt.json"
mkdir -p artifacts
SNAPSHOT_DIR=$(mktemp -d)
trap 'rm -rf -- "$SNAPSHOT_DIR"' EXIT
CONFIG_SNAPSHOT="$SNAPSHOT_DIR/production_agent.yaml"
install -m 0600 -- "$CONFIG" "$CONFIG_SNAPSHOT"

CONFIG_SHA256="$(sha256sum "$CONFIG_SNAPSHOT" | awk '{print $1}')"
cosign sign-blob --yes \
  --key "$CONFIG_SIGNING_KMS_URI" \
  --bundle "$SIGNATURE" \
  "$CONFIG_SNAPSHOT"
cosign verify-blob \
  --key "/opt/aidefend/trust/agent-config-signing.pub" \
  --bundle "$SIGNATURE" \
  "$CONFIG_SNAPSHOT"

jq -n \
  --arg schema "aidefend.agent-config-signing.v1" \
  --arg config_sha256 "$CONFIG_SHA256" \
  --arg signing_key "$CONFIG_SIGNING_KMS_URI" \
  --arg repository "$GITHUB_REPOSITORY" \
  --arg commit "$GITHUB_SHA" \
  --arg workflow_ref "$GITHUB_WORKFLOW_REF" \
  '{schema_version:$schema,status:"PASS",config_sha256:$config_sha256,signing_key:$signing_key,repository:$repository,commit_sha:$commit,workflow_ref:$workflow_ref}' \
  > "$EVIDENCE"
Verification and evidence

Upload the config, detached signature, and receipt as one immutable artifact set after schema/policy validation. Independently verify the signature and compare the receipt's commit, workflow ref, config digest, and approved immutable KMS key identity. Missing artifacts are INSUFFICIENT_DATA; a digest or signature mismatch is FAIL; signer/tool malfunction is ERROR. A signature proves approved origin and bytes, not semantic safety.

Action: keep the signing identity in KMS/HSM, restrict Sign to the protected release workload, and publish the exact bytes plus signature and receipt together.

AID-H-021.002-G002

Require the agent to verify the configuration signature at startup before executing.
Concept

Verify and parse the same bounded regular-file bytes. Do not verify a pathname and then reopen it: that creates a verifier/use race. The public trust key and JSON Schema are pinned in the workload image; request or environment data may not select either.

Production startup loader
# File: agent/verified_config.py
from __future__ import annotations

import hashlib
import json
import math
import os
import stat
import subprocess
import tempfile
from pathlib import Path

import jsonschema
import yaml

CONFIG = Path("/app/configs/production_agent.yaml")
SIGNATURE = Path("/app/configs/production_agent.yaml.sig")
TRUST_KEY = Path("/opt/aidefend/trust/agent-config-signing.pub")
SCHEMA = Path("/opt/aidefend/policy/agent-config.schema.json")
MAX_CONFIG_BYTES = int(os.environ["VERIFIED_AGENT_CONFIG_MAX_BYTES"])
MAX_SIGNATURE_BYTES = int(os.environ["VERIFIED_AGENT_CONFIG_SIGNATURE_MAX_BYTES"])
COSIGN_TIMEOUT_SECONDS = float(os.environ["VERIFIED_AGENT_CONFIG_SIGNATURE_TIMEOUT_SECONDS"])
if (
    min(MAX_CONFIG_BYTES, MAX_SIGNATURE_BYTES) < 1
    or not math.isfinite(COSIGN_TIMEOUT_SECONDS)
    or COSIGN_TIMEOUT_SECONDS <= 0
):
    raise RuntimeError("verified agent-config bounds are invalid")


def stable_bytes(path: Path, limit: int) -> bytes:
    flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
    fd = os.open(path, flags)
    try:
        before = os.fstat(fd)
        if not stat.S_ISREG(before.st_mode) or before.st_size < 1 or before.st_size > limit:
            raise ValueError(f"invalid bounded regular file: {path}")
        chunks: list[bytes] = []
        remaining = before.st_size
        while remaining:
            chunk = os.read(fd, min(remaining, 64 * 1024))
            if not chunk:
                raise OSError("short read")
            chunks.append(chunk)
            remaining -= len(chunk)
        if os.read(fd, 1):
            raise OSError("file grew during read")
        after = os.fstat(fd)
        if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (
            after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns
        ):
            raise OSError("file changed during read")
        return b"".join(chunks)
    finally:
        os.close(fd)


class StrictSafeLoader(yaml.SafeLoader):
    pass


def construct_mapping(loader: StrictSafeLoader, node, deep=False):
    loader.flatten_mapping(node)
    result = {}
    for key_node, value_node in node.value:
        key = loader.construct_object(key_node, deep=deep)
        if key in result:
            raise ValueError(f"duplicate configuration key: {key!r}")
        result[key] = loader.construct_object(value_node, deep=deep)
    return result


StrictSafeLoader.add_constructor(
    yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping
)


def load_verified_config() -> tuple[dict, dict]:
    config_bytes = stable_bytes(CONFIG, MAX_CONFIG_BYTES)
    signature_bytes = stable_bytes(SIGNATURE, MAX_SIGNATURE_BYTES)
    with tempfile.TemporaryDirectory(prefix="aidefend-config-") as directory:
        root = Path(directory)
        config_copy = root / "config.yaml"
        signature_copy = root / "config.sig"
        config_copy.write_bytes(config_bytes)
        signature_copy.write_bytes(signature_bytes)
        subprocess.run(
            [
                "cosign", "verify-blob", "--key", str(TRUST_KEY),
                "--bundle", str(signature_copy), str(config_copy),
            ],
            check=True, capture_output=True, text=True, timeout=COSIGN_TIMEOUT_SECONDS,
        )

    parsed = yaml.load(config_bytes.decode("utf-8", "strict"), Loader=StrictSafeLoader)
    schema = json.loads(stable_bytes(SCHEMA, MAX_CONFIG_BYTES))
    jsonschema.Draft202012Validator(
        schema, format_checker=jsonschema.FormatChecker()
    ).validate(parsed)
    receipt = {
        "schema_version": "aidefend.agent-config-startup.v1",
        "status": "PASS",
        "config_sha256": hashlib.sha256(config_bytes).hexdigest(),
        "signature_sha256": hashlib.sha256(signature_bytes).hexdigest(),
        "schema_sha256": hashlib.sha256(
            json.dumps(schema, sort_keys=True, separators=(",", ":")).encode()
        ).hexdigest(),
    }
    return parsed, receipt

AID-H-021.002-G003

Reject unsigned local configuration overrides and dangerous environment-based overrides at agent startup.
Concept

After the agent verifies the signed baseline configuration, it must refuse to load any untrusted local override file and must reject environment variables that attempt to weaken security-critical behavior. This is a runtime, fail-closed control: local convenience settings must never outrank the approved signed configuration.

Step 1: Define a strict configuration precedence model
Step 2: Fail closed on unsigned local files and forbidden overrides
# File: agent/startup_config.py
from __future__ import annotations

import os
from pathlib import Path
from typing import Any

from agent.verified_config import load_verified_config


class SecurityException(RuntimeError):
    pass


LOCAL_OVERRIDE_PATHS = [
    Path('./local_config.yaml'),
    Path.home() / '.agent' / 'config.yaml',
]

ALLOWED_ENV_OVERRIDES = {
    'AGENT_LOG_LEVEL',
    'AGENT_WORKER_COUNT',
}

BLOCKED_ENV_OVERRIDES = {
    'AGENT_UNSAFE_MODE',
    'AGENT_DISABLE_CONFIRMATIONS',
    'AGENT_DISABLE_SANDBOX',
    'AGENT_POLICY_BUNDLE',
}


def reject_local_override_files() -> None:
    offenders = [str(p) for p in LOCAL_OVERRIDE_PATHS if p.exists()]
    if offenders:
        raise SecurityException(
            'Unsigned local configuration overrides are forbidden: ' + ', '.join(offenders)
        )


def apply_allowed_env_overrides(config: dict[str, Any]) -> dict[str, Any]:
    for key in BLOCKED_ENV_OVERRIDES:
        if key in os.environ:
            raise SecurityException(f'Forbidden security override via environment variable: {key}')

    if 'AGENT_LOG_LEVEL' in os.environ:
        config.setdefault('runtime', {})['log_level'] = os.environ['AGENT_LOG_LEVEL']
    if 'AGENT_WORKER_COUNT' in os.environ:
        config.setdefault('runtime', {})['worker_count'] = int(os.environ['AGENT_WORKER_COUNT'])

    unexpected = [
        k for k in os.environ
        if k.startswith('AGENT_') and k not in ALLOWED_ENV_OVERRIDES and k not in BLOCKED_ENV_OVERRIDES
    ]
    if unexpected:
        raise SecurityException(
            'Unknown AGENT_* override variables are not permitted: ' + ', '.join(sorted(unexpected))
        )

    return config


def load_startup_configuration() -> tuple[dict[str, Any], dict]:
    # Exact dependency: H-021.002 guidance 2 returns the parsed bytes and its
    # local signature/schema receipt. Missing or non-PASS receipt fails startup.
    config, verification = load_verified_config()
    if verification.get('status') != 'PASS':
        raise SecurityException('signed baseline did not pass local verification')
    reject_local_override_files()
    effective = apply_allowed_env_overrides(config)
    return effective, {
        **verification,
        'override_policy': 'allowlisted-non-security-fields-v1',
        'effective_status': 'PASS',
    }
Step 3: Emit auditable startup failures

When startup rejects a local override or blocked environment variable, emit a structured security event with the host identity, agent instance, offending source, and reason code. Do not continue in degraded mode.

Operational notes

Action: Treat the signed configuration artifact as the single source of truth at startup. Refuse unsigned local override files, block security-sensitive environment overrides, and fail closed with an auditable security event when an override attempt is detected.

AID-H-021.002-G004

Reject runtime configuration changes that do not originate from a signed, approved source of truth.
Concept

Startup verification is not enough if the agent supports live reload, hot policy updates, or dynamic prompt/config refresh. Every runtime change request must be tied to a signed config bundle and an approved deployment record from the control plane. Unsigned or ad-hoc reload requests must be rejected.

Step 1: Require every reload request to carry deployment metadata
{
  "deployment_id": "cfg-rollout-2026-04-08-17",
  "config_digest": "sha256:cfb79f4ed61e88f248d69a56b7b22b0149f3d6544413eef235659018b4d558cb",
  "config_url": "s3://aidefend-configs/prod/agent-v17.yaml",
  "signature_url": "s3://aidefend-configs/prod/agent-v17.yaml.sig"
}
Step 2: Verify the signed artifact and deployment approval before applying it
# File: runtime/reload_gate.py
from __future__ import annotations

from dataclasses import dataclass
from typing import Callable


class ReloadDenied(RuntimeError):
    pass


FetchDeploymentRecord = Callable[[str], tuple[dict, dict]]
VerifyConfigBundle = Callable[[str, str, str], dict]


@dataclass(frozen=True)
class DeploymentControlPlane:
    fetch_authenticated_record: FetchDeploymentRecord

    def get_authenticated_record(self, deployment_id: str) -> tuple[dict, dict]:
        result = self.fetch_authenticated_record(deployment_id)
        if not isinstance(result, tuple) or len(result) != 2 or any(not isinstance(value, dict) for value in result):
            raise RuntimeError("control plane returned an invalid authenticated record")
        record, receipt = result
        if receipt.get("status") != "PASS" or not receipt.get("record_sha256"):
            raise RuntimeError("control-plane identity or readback verification failed")
        return record, receipt


@dataclass(frozen=True)
class ConfigBundleVerifier:
    verify_bundle: VerifyConfigBundle

    def verify_exact_bundle(
        self, *, config_url: str, signature_url: str, expected_digest: str
    ) -> dict:
        receipt = self.verify_bundle(config_url, signature_url, expected_digest)
        if not isinstance(receipt, dict) or receipt.get("status") != "PASS":
            raise RuntimeError("local config bundle verification failed")
        if receipt.get("config_digest") != expected_digest:
            raise RuntimeError("config bundle receipt is not bound to the requested digest")
        return receipt


RELOAD_FIELDS = {
    "deployment_id", "config_digest", "config_url", "signature_url",
}
APPROVAL_FIELDS = RELOAD_FIELDS | {
    "status", "agent_id", "approved_at", "expires_at", "policy_version",
}


def authorize_runtime_reload(
    reload_request: dict,
    *,
    expected_agent_id: str,
    control_plane: DeploymentControlPlane,
    verifier: ConfigBundleVerifier,
) -> dict:
    if not isinstance(reload_request, dict) or set(reload_request) != RELOAD_FIELDS:
        raise ReloadDenied("reload_request_schema_mismatch")
    if any(not isinstance(value, str) or not value for value in reload_request.values()):
        raise ReloadDenied("reload_request_field_invalid")
    approved, control_plane_receipt = control_plane.get_authenticated_record(
        reload_request["deployment_id"]
    )
    if not isinstance(approved, dict) or set(approved) != APPROVAL_FIELDS:
        raise ReloadDenied("approved_deployment_schema_mismatch")
    if control_plane_receipt.get("status") != "PASS":
        raise ReloadDenied("control_plane_identity_not_verified")
    if approved["status"] != "approved":
        raise ReloadDenied("deployment_not_approved")
    if approved["agent_id"] != expected_agent_id:
        raise ReloadDenied("deployment_agent_mismatch")
    for field in RELOAD_FIELDS:
        if approved[field] != reload_request[field]:
            raise ReloadDenied(f"deployment_{field}_mismatch")
    if approved["config_digest"] != reload_request["config_digest"]:
        raise ReloadDenied("config_digest_mismatch")
    local_receipt = verifier.verify_exact_bundle(
        config_url=reload_request["config_url"],
        signature_url=reload_request["signature_url"],
        expected_digest=reload_request["config_digest"],
    )
    if local_receipt.get("status") != "PASS":
        raise ReloadDenied("local_bundle_verification_failed")
    return {
        "schema_version": "aidefend.agent-config-reload.v1",
        "status": "PASS",
        "deployment_id": approved["deployment_id"],
        "agent_id": approved["agent_id"],
        "config_digest": approved["config_digest"],
        "policy_version": approved["policy_version"],
        "control_plane_receipt": control_plane_receipt,
        "local_verification_receipt": local_receipt,
    }

DeploymentControlPlane must authenticate a fixed control-plane origin with workload identity/mTLS, validate the exact response schema and approval expiry, and bind its receipt to deployment ID, agent ID, policy version, and response digest. ConfigBundleVerifier must fetch immutable object versions from allowlisted storage and locally verify the exact bytes, digest, signature, and schema using a pinned trust root. Missing dependency evidence is INSUFFICIENT_DATA; a mismatch or expired/non-approved record is FAIL; transport/verifier malfunction is ERROR. The returned local receipt is this guidance's evidence; an upstream approval is not a local pass.

Action: Accept runtime config changes only when this local gate returns PASS, then apply them atomically and retain both dependency receipts. Never let operators hot-edit security-sensitive configuration directly on the host or inside the container.

AID-H-021.002-G005

Verify agent tool and plugin files against a signed hash manifest before startup or hot-reload.
Concept

Configuration integrity alone is not enough if the runtime also loads executable tool or plugin files from disk. Treat those startup-loaded artifacts as part of the runtime trust boundary: build a signed manifest in CI/CD, verify every listed file before the service starts, and fail closed on any mismatch or unexpected file.

Step 1: Generate a signed tool manifest during the trusted build
# File: ci/build_tool_manifest.sh
set -euo pipefail

# Manifest paths are relative to the tool root in both producer and verifier.
(cd agent/tools && find . -type f -name '*.py' -print0 | sort -z | xargs -0 -r sha256sum --zero) > tool_manifest.sha256

cosign sign-blob --yes   --key "$CONFIG_SIGNING_KMS_URI"   --bundle tool_manifest.sha256.sig   tool_manifest.sha256
Step 2: Verify the signed manifest and every startup-loaded file
# File: agent/verify_tools.py
from __future__ import annotations

import hashlib
import json
import math
import os
import stat
import subprocess
import tempfile
from pathlib import Path


class IntegrityError(RuntimeError):
    pass


MAX_MANIFEST_BYTES = int(os.environ["VERIFIED_H021_TOOL_MANIFEST_MAX_BYTES"])
MAX_SIGNATURE_BYTES = int(os.environ["VERIFIED_H021_TOOL_SIGNATURE_MAX_BYTES"])
MAX_TOOL_BYTES = int(os.environ["VERIFIED_H021_TOOL_FILE_MAX_BYTES"])
COSIGN_TIMEOUT_SECONDS = float(os.environ["VERIFIED_H021_TOOL_COSIGN_TIMEOUT_SECONDS"])
if (
    min(MAX_MANIFEST_BYTES, MAX_SIGNATURE_BYTES, MAX_TOOL_BYTES) < 1
    or not math.isfinite(COSIGN_TIMEOUT_SECONDS)
    or COSIGN_TIMEOUT_SECONDS <= 0
):
    raise RuntimeError("verified tool-integrity bounds are invalid")


def stable_bytes(path: Path, maximum_bytes: int) -> bytes:
    if maximum_bytes < 1 or not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "O_CLOEXEC"):
        raise RuntimeError("secure bounded tool-file read is unavailable")
    fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
    try:
        before = os.fstat(fd)
        if not stat.S_ISREG(before.st_mode) or before.st_size < 1 or before.st_size > maximum_bytes:
            raise IntegrityError("tool-integrity input is not a bounded regular file")
        chunks = []
        remaining = maximum_bytes + 1
        while remaining:
            chunk = os.read(fd, min(remaining, 1024 * 1024))
            if not chunk:
                break
            chunks.append(chunk)
            remaining -= len(chunk)
        payload = b"".join(chunks)
        after = os.fstat(fd)
        if (
            len(payload) != before.st_size
            or len(payload) > maximum_bytes
            or (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns)
            != (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns)
        ):
            raise IntegrityError("tool-integrity input changed during read")
        return payload
    finally:
        os.close(fd)


def sha256_file(path: Path) -> str:
    return hashlib.sha256(stable_bytes(path, MAX_TOOL_BYTES)).hexdigest()


def load_manifest(path: Path, signature: Path, trust_key: Path) -> dict[str, str]:
    raw = stable_bytes(path, MAX_MANIFEST_BYTES)
    sig = stable_bytes(signature, MAX_SIGNATURE_BYTES)
    with tempfile.TemporaryDirectory(prefix="aidefend-tools-") as directory:
        root = Path(directory)
        manifest_copy = root / "manifest"
        signature_copy = root / "manifest.sig"
        manifest_copy.write_bytes(raw)
        signature_copy.write_bytes(sig)
        subprocess.run(
            [
                "cosign", "verify-blob", "--key", str(trust_key),
                "--bundle", str(signature_copy), str(manifest_copy),
            ],
            check=True, capture_output=True, text=True, timeout=COSIGN_TIMEOUT_SECONDS,
        )
    expected: dict[str, str] = {}
    for record in raw.split(b"\0"):
        if not record:
            continue
        file_hash, separator, rel_path = record.partition(b"  ")
        if separator != b"  " or len(file_hash) != 64:
            raise IntegrityError("signed tool manifest record is malformed")
        decoded_path = rel_path.decode("utf-8", "strict")
        if decoded_path.startswith("./"):
            decoded_path = decoded_path[2:]
        if (not decoded_path or "\\" in decoded_path or decoded_path.startswith("/")
                or any(part in {"", ".", ".."} for part in decoded_path.split("/"))):
            raise IntegrityError("tool manifest path is not canonical and root-relative")
        if decoded_path in expected:
            raise IntegrityError("signed tool manifest contains a duplicate path")
        bytes.fromhex(file_hash.decode("ascii"))
        expected[decoded_path] = file_hash.decode("ascii")
    if not expected:
        raise IntegrityError("signed tool manifest is empty")
    return expected


def verify_tool_tree(
    tool_root: Path, manifest_path: Path, signature: Path, trust_key: Path
) -> dict:
    expected = load_manifest(manifest_path, signature, trust_key)

    discovered = sorted(
        str(path.relative_to(tool_root)).replace("\\", "/")
        for path in tool_root.rglob("*.py") if path.is_file() and not path.is_symlink()
    )

    if sorted(expected.keys()) != discovered:
        raise IntegrityError("Tool inventory mismatch between runtime tree and signed manifest")

    for rel_path, expected_hash in expected.items():
        actual_hash = sha256_file(tool_root / rel_path)
        if actual_hash != expected_hash:
            raise IntegrityError(f"Tool hash mismatch for {rel_path}")
    return {
        "schema_version": "aidefend.agent-tool-integrity.v1",
        "status": "PASS",
        "manifest_sha256": hashlib.sha256(
            stable_bytes(manifest_path, MAX_MANIFEST_BYTES)
        ).hexdigest(),
        "tool_count": len(expected),
    }
Step 3: Refuse startup on integrity failure and emit a security event

If manifest signature verification fails, if the runtime tree differs from the approved manifest, or if any file hash mismatches, the agent must not start. Emit a structured integrity event to SIEM with the manifest version, failing file, and workload identity.

Action: Treat tool and plugin file verification as a startup gate, not a best-effort scan. If the runtime cannot prove that every executable helper matches the signed manifest, block launch and escalate.

AID-H-021.002-G006

Continuously reconcile running configuration to the signed desired state and automatically restore approved values when drift is detected.
Concept

Runtime integrity is stronger when the platform continuously compares live config with the signed desired state. If a process modifies prompt text, tool policy, network allowlists, or safety thresholds in memory or on disk, the reconciler should restore the approved version and emit a high-signal drift event.

Step 1: Publish the signed desired-state digest
{
  "agent_id": "support-bot-prod",
  "desired_config_digest": "sha256:cfb79f4ed61e88f248d69a56b7b22b0149f3d6544413eef235659018b4d558cb",
  "policy_version": "cfg-v17",
  "source_url": "s3://aidefend-configs/prod/agent-v17.yaml"
}
Step 2: Reconcile live state against that digest on a fixed cadence
# File: runtime/config_reconciler.py
from __future__ import annotations

import hashlib
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

from agent.verified_config import stable_bytes

CONFIG_PATH = Path("/app/configs/production_agent.yaml")


LoadDesiredState = Callable[[], tuple[dict, dict]]
RestoreSignedConfig = Callable[[dict], dict]


@dataclass(frozen=True)
class DesiredStateProvider:
    load_desired_state: LoadDesiredState

    def load_verified(self) -> tuple[dict, dict]:
        result = self.load_desired_state()
        if not isinstance(result, tuple) or len(result) != 2 or any(not isinstance(value, dict) for value in result):
            raise RuntimeError("desired-state provider returned an invalid contract")
        desired, receipt = result
        if receipt.get("status") != "PASS" or not receipt.get("desired_state_sha256"):
            raise RuntimeError("desired-state signature or readback verification failed")
        return desired, receipt


@dataclass(frozen=True)
class SignedConfigRestorer:
    restore_signed_config: RestoreSignedConfig

    def restore_exact(self, desired: dict) -> dict:
        receipt = self.restore_signed_config(desired)
        if not isinstance(receipt, dict) or receipt.get("status") != "PASS":
            raise RuntimeError("signed config restore failed")
        if receipt.get("restored_config_digest") != desired.get("desired_config_digest"):
            raise RuntimeError("restored config readback differs from signed desired state")
        return receipt


def sha256_file(path: Path) -> str:
    return "sha256:" + hashlib.sha256(stable_bytes(path, 1_000_000)).hexdigest()


def reconcile_once(
    provider: DesiredStateProvider, restorer: SignedConfigRestorer
) -> dict:
    desired, desired_receipt = provider.load_verified()
    required = {
        "agent_id", "desired_config_digest", "policy_version", "source_url",
    }
    if (
        not isinstance(desired, dict) or set(desired) != required
        or desired_receipt.get("status") != "PASS"
    ):
        raise RuntimeError("verified desired-state contract is unavailable")
    live_digest = sha256_file(CONFIG_PATH)
    if live_digest == desired["desired_config_digest"]:
        return {
            "schema_version": "aidefend.config-reconciliation.v1",
            "status": "PASS",
            "action": "no_drift",
            "agent_id": desired["agent_id"],
            "observed_digest": live_digest,
            "policy_version": desired["policy_version"],
            "desired_state_receipt": desired_receipt,
        }
    restore_receipt = restorer.restore_exact(desired)
    restored_digest = sha256_file(CONFIG_PATH)
    if (
        restore_receipt.get("status") != "PASS"
        or restored_digest != desired["desired_config_digest"]
    ):
        raise RuntimeError("signed configuration restore did not read back")
    return {
        "schema_version": "aidefend.config-reconciliation.v1",
        "status": "FAIL",
        "action": "drift_restored",
        "agent_id": desired["agent_id"],
        "expected_digest": desired["desired_config_digest"],
        "observed_digest": live_digest,
        "restored_digest": restored_digest,
        "policy_version": desired["policy_version"],
        "desired_state_receipt": desired_receipt,
        "restore_receipt": restore_receipt,
    }


def reconcile_loop(
    provider: DesiredStateProvider, restorer: SignedConfigRestorer
) -> None:
    while True:
        print(reconcile_once(provider, restorer))
        time.sleep(30)

Action: Run reconciliation from a trusted sidecar, controller, or host agent that can restore the signed desired state without trusting the compromised process. Treat every auto-restore event as an incident lead, not just a maintenance message.

AID-H-021.002-G007

Generate verifiable supply chain attestations for agent configurations.
Concept

Generate provenance only after the exact validated and signed configuration bytes are final. The attestation is a separate claim about repository, workflow, commit, and build path; it does not replace the detached configuration signature or prove that the policy content is safe.

Generate GitHub artifact provenance
permissions:
  contents: read
  id-token: write
  attestations: write

steps:
  - name: Attest final configuration
    uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
    with:
      subject-path: configs/production_agent.yaml
Verify before deployment
set -euo pipefail
CONFIG="configs/production_agent.yaml"
EXPECTED_REPOSITORY="acme/agent-config"
EXPECTED_WORKFLOW="acme/agent-config/.github/workflows/build-config.yml"
mkdir -p artifacts

gh attestation verify "$CONFIG" \
  --repo "$EXPECTED_REPOSITORY" \
  --signer-workflow "$EXPECTED_WORKFLOW" \
  --format json \
  > artifacts/config-provenance-verification.json
test -s artifacts/config-provenance-verification.json
Outcome and evidence

Bind the verification JSON to the configuration digest, detached-signature receipt, expected repository/workflow, commit and protected ref policy, verifier version, and deployment ID. Missing online/offline trust material is INSUFFICIENT_DATA; identity/digest mismatch is FAIL; verifier failure is ERROR. The deployment gate must separately evaluate organization policy over the verified statement; a cryptographically valid but unapproved workflow is not PASS.

Action: attest the final configuration in the protected build workflow and require independent provenance verification before deployment.

AID-H-021.002-G008

Implement secure key management and rotation for signing keys.
Concept:

The security of your signing process depends on the security of your signing keys. These keys must be managed in a secure system (like a KMS or HSM), have their access tightly controlled, and be rotated periodically to limit the impact of a potential key compromise.

Use a KMS for Signing Operations

Instead of handling raw key files in your CI/CD runner, use a Key Management Service (KMS) for all signing operations. The private key never leaves the KMS.

# File: signing/kms_sign.py
from __future__ import annotations

import base64
import hashlib
from pathlib import Path

import boto3
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, utils

KMS_KEY_ID = 'alias/agent-config-signing-key'
ALGORITHM = 'RSASSA_PSS_SHA_256'


def sign_file(path: str) -> dict:
    payload = Path(path).read_bytes()
    digest = hashlib.sha256(payload).digest()
    kms = boto3.client('kms')
    response = kms.sign(
        KeyId=KMS_KEY_ID,
        Message=digest,
        MessageType='DIGEST',
        SigningAlgorithm=ALGORITHM,
    )
    public_key = kms.get_public_key(KeyId=response['KeyId'])['PublicKey']

    verifier = serialization.load_der_public_key(public_key)
    verifier.verify(
        response['Signature'],
        digest,
        padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH),
        utils.Prehashed(hashes.SHA256()),
    )
    return {
        'schema': 'aidefend.kms-signature/v1',
        'key_id': response['KeyId'],
        'algorithm': ALGORITHM,
        'payload_sha256': digest.hex(),
        'signature_b64': base64.b64encode(response['Signature']).decode('ascii'),
        'public_key_der_b64': base64.b64encode(public_key).decode('ascii'),
    }
Rotate without breaking verification

For asymmetric signing keys, create and approve a successor key, publish both old and new key IDs/public keys to verifiers, switch the signer alias to the successor, and keep the old public key accepted for the versioned artifact-retention or rollout grace period. Independently read back the key ARN returned by Sign; never record only the mutable alias. Disable signing with the old key after all producers have moved, and remove old verification trust only after every still-valid artifact has expired or been re-signed.

Verification and evidence

Verify a valid signature locally, then test a modified payload, modified signature, unauthorized CI role, disabled key, and alias rollover. Preserve the payload digest, immutable KMS key ARN, public-key fingerprint, algorithm, signature, CI workload identity, KMS audit event, verifier result, rotation phase, and explicit PASS/FAIL/ERROR verdict.

Action: Store your configuration signing keys in a hardware-backed KMS. Use an approved asymmetric-key rollover runbook and IAM policies that restrict signing to the intended CI workload identity and key ARN.

Open-source tools

Source-available / open-weight 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-H-021.002: Runtime Integrity Enforcement (Signed Configurations) | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version