AID-M-005.002: Policy-as-Code Configuration Baselines & Posture Release Gates
Pillars: infra
Lifecycle: building, validation
Scope boundary
Owns signed policy-as-code configuration baselines, posture SLO and SLI definitions, scoring criteria, and build or release conformance gates. It does not own continuous runtime CSPM or drift findings.
AID-M-005.001: AID-M-005.002 evaluates release configuration against signed policy and measurable criteria; AID-M-005.001 authors the underlying secure templates and validates their static structure. Gate evidence proves conformance of a measured release population, while template review proves only the design artifact.
Implementation guidance
- AID-M-005.002-G001: Author security baselines as policy-as-code and wire them into CI gates.
- AID-M-005.002-G002: Define posture SLO/SLI and scoring rules; document release gates.
- AID-M-005.002-G003: Produce and sign a Baseline Manifest to ensure versioning and immutability.
AID-M-005.002-G001
Scope and input contract
Evaluate Terraform JSON configuration, where resources use the real resource.<type>.<name> structure. This example deliberately does not claim to parse HCL .tf. If HCL is authoritative, first render the complete declared HCL population into Terraform JSON with a renderer pinned by immutable version or digest, compare source and rendered population manifests, and reject a partial or unpinned rendering.
Rego policy
# File: policy/aidefend-baseline/aidefend_baseline.rego
package aidefend.baseline
import rego.v1
resources := object.get(input, "resource", null)
safe_resources := resources if {
is_object(resources)
} else := {}
security_groups := object.get(safe_resources, "aws_security_group", {})
instances := object.get(safe_resources, "aws_instance", {})
open_cidrs := {"0.0.0.0/0", "::/0"}
invalid_resource_root if {
not is_object(resources)
}
invalid_resource_root if {
is_object(resources)
count(resources) == 0
}
invalid_literal_cidr(cidr) if {
not is_string(cidr)
}
invalid_literal_cidr(cidr) if {
is_string(cidr)
not net.cidr_is_valid(cidr)
}
deny contains "Terraform JSON must contain a non-empty resource object" if {
invalid_resource_root
}
deny contains "resource.aws_security_group must be an object" if {
is_object(resources)
not is_object(security_groups)
}
deny contains "resource.aws_instance must be an object" if {
is_object(resources)
not is_object(instances)
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
not is_object(group)
msg := sprintf("aws_security_group.%s must be an object", [name])
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
is_object(group)
ingress := object.get(group, "ingress", [])
not is_array(ingress)
msg := sprintf("aws_security_group.%s ingress must be a literal array", [name])
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
is_object(group)
some index, ingress in object.get(group, "ingress", [])
not is_object(ingress)
msg := sprintf("aws_security_group.%s ingress[%d] must be an object", [name, index])
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
is_object(group)
some index, ingress in object.get(group, "ingress", [])
is_object(ingress)
cidrs := object.get(ingress, "cidr_blocks", null)
not is_array(cidrs)
msg := sprintf("aws_security_group.%s ingress[%d] must declare literal cidr_blocks", [name, index])
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
is_object(group)
some index, ingress in object.get(group, "ingress", [])
is_object(ingress)
cidrs := object.get(ingress, "cidr_blocks", [])
is_array(cidrs)
count(cidrs) == 0
msg := sprintf("aws_security_group.%s ingress[%d] has no CIDR", [name, index])
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
is_object(group)
some index, ingress in object.get(group, "ingress", [])
is_object(ingress)
some cidr in object.get(ingress, "cidr_blocks", [])
invalid_literal_cidr(cidr)
msg := sprintf("aws_security_group.%s ingress[%d] has an unresolved or invalid CIDR", [name, index])
}
deny contains msg if {
is_object(security_groups)
some name, group in security_groups
is_object(group)
some index, ingress in object.get(group, "ingress", [])
is_object(ingress)
some cidr in object.get(ingress, "cidr_blocks", [])
cidr in open_cidrs
msg := sprintf("aws_security_group.%s ingress[%d] permits a globally open CIDR", [name, index])
}
deny contains msg if {
is_object(instances)
some name, instance in instances
not is_object(instance)
msg := sprintf("aws_instance.%s must be an object", [name])
}
deny contains msg if {
is_object(instances)
some name, instance in instances
is_object(instance)
object.get(instance, "associate_public_ip_address", null) != false
msg := sprintf("aws_instance.%s must set associate_public_ip_address to literal false", [name])
}
This bounded policy requires every inline security-group ingress rule to have a non-empty list of literal, valid IP CIDRs and every declared EC2 instance to set public-IP association explicitly to false. It also rejects missing, empty, or non-object resource roots, malformed resource-type maps, resource instances, and ingress entries instead of treating skipped evaluation as success. Add separately tested rules if the approved architecture uses security-group references or another Terraform resource model.
Terraform JSON fixtures
# File: terraform/rendered/baseline.tf.json
{"resource":{"aws_security_group":{"app":{"ingress":[{"from_port":443,"to_port":443,"protocol":"tcp","cidr_blocks":["10.20.0.0/16"]}]}},"aws_instance":{"app":{"associate_public_ip_address":false}}}}
# File: testdata/clean.tf.json
{"resource":{"aws_security_group":{"app":{"ingress":[{"from_port":443,"to_port":443,"protocol":"tcp","cidr_blocks":["10.20.0.0/16"]}]}},"aws_instance":{"app":{"associate_public_ip_address":false}}}}
# File: testdata/open-cidr.tf.json
{"resource":{"aws_security_group":{"app":{"ingress":[{"cidr_blocks":["0.0.0.0/0"]}]}}}}
# File: testdata/unresolved-cidr.tf.json
{"resource":{"aws_security_group":{"app":{"ingress":[{"cidr_blocks":"${var.allowed_ingress_cidrs}"}]}}}}
# File: testdata/public-ip.tf.json
{"resource":{"aws_instance":{"app":{"associate_public_ip_address":true}}}}
# File: testdata/missing-resource.tf.json
{}
# File: testdata/non-object-resource.tf.json
{"resource":[]}
# File: testdata/malformed-resource-maps.tf.json
{"resource":{"aws_security_group":[],"aws_instance":[]}}
# File: testdata/malformed-resource-rows.tf.json
{"resource":{"aws_security_group":{"bad_group":"not-an-object","bad_ingress":{"ingress":["not-an-object"]}},"aws_instance":{"bad_instance":"not-an-object"}}}
The last four fixtures deliberately cover absent or non-object resource roots and malformed Terraform JSON shapes, proving the policy returns an explicit deny before field-level checks.
Declare the complete production population
# File: policy/terraform-json-population.txt
terraform/rendered/baseline.tf.json
Validate Conftest JSON reports independently
# File: ci/validate_conftest_report.py
#!/usr/bin/env python3
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument("--report", required=True)
parser.add_argument("--namespace", required=True)
parser.add_argument("--expect-file", action="append", default=[])
parser.add_argument("--expect-finding", action="append", default=[])
parser.add_argument("--require-pass", action="store_true")
args = parser.parse_args()
with open(args.report, encoding="utf-8") as handle:
rows = json.load(handle)
if not isinstance(rows, list) or not rows:
raise SystemExit("Conftest report must be a non-empty JSON list")
expected_files = sorted(args.expect_file)
actual_files = [row.get("filename") for row in rows if isinstance(row, dict)]
if len(actual_files) != len(rows) or sorted(actual_files) != expected_files:
raise SystemExit(f"Conftest filenames differ: expected={expected_files!r} actual={sorted(actual_files)!r}")
messages = []
for row in rows:
if row.get("namespace") != args.namespace:
raise SystemExit(f"Unexpected namespace for {row.get('filename')!r}")
successes = row.get("successes")
if not isinstance(successes, int) or isinstance(successes, bool) or successes < 0:
raise SystemExit(f"Invalid successes count for {row.get('filename')!r}")
for field in ("warnings", "exceptions"):
if row.get(field, []):
raise SystemExit(f"Unexpected {field} for {row.get('filename')!r}")
failures = row.get("failures", [])
if not isinstance(failures, list):
raise SystemExit(f"Invalid failures field for {row.get('filename')!r}")
messages.extend(str(item.get("msg", "")) for item in failures if isinstance(item, dict))
if args.require_pass and (successes <= 0 or failures):
raise SystemExit(f"No positive policy evaluation for {row.get('filename')!r}")
if args.require_pass and messages:
raise SystemExit("PASS report contains findings")
if not args.require_pass and not messages:
raise SystemExit("Negative fixture produced no policy finding")
for expected in args.expect_finding:
if not any(expected in message for message in messages):
raise SystemExit(f"Expected finding not present: {expected!r}")
GitHub Actions gate
# File: .github/workflows/iac-baseline.yml
name: IaC Baseline Check
on:
pull_request:
permissions:
contents: read
jobs:
conftest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- name: Test policy and gate the declared population
shell: bash
run: |
set -euo pipefail
image='openpolicyagent/conftest:v0.65.0@sha256:00a89cb5755b0cc9898868ffad8f11e3411fedb7074ea0f3e1f1ababa0a4cfe8'
namespace='aidefend.baseline'
mapfile -t declared < <(sed -e '/^[[:space:]]*#/d' -e '/^[[:space:]]*$/d' policy/terraform-json-population.txt)
(( ${#declared[@]} > 0 )) || { echo 'Terraform JSON population is empty' >&2; exit 2; }
for path in "${declared[@]}"; do
[[ "$path" == terraform/rendered/*.tf.json && "$path" != *'..'* ]] || { echo "Unsafe population path: $path" >&2; exit 2; }
done
mapfile -t declared_sorted < <(printf '%s\n' "${declared[@]}" | LC_ALL=C sort -u)
(( ${#declared_sorted[@]} == ${#declared[@]} )) || { echo 'Population manifest has duplicates' >&2; exit 2; }
mapfile -d '' discovered < <(find terraform/rendered -type f -name '*.tf.json' -print0 | LC_ALL=C sort -z)
diff -u <(printf '%s\n' "${declared_sorted[@]}") <(printf '%s\n' "${discovered[@]}")
conftest=(docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/workspace:ro" -w /workspace "$image" test --policy policy/aidefend-baseline --namespace "$namespace" --output json)
validator=(python3 ci/validate_conftest_report.py --namespace "$namespace")
mkdir -p fixture-reports
run_case() {
local expected="$1" file="$2" stem report rc finding
shift 2
stem="$(basename "$file" .tf.json)"
report="fixture-reports/$stem.json"
set +e
"${conftest[@]}" "$file" >"$report" 2>"${report%.json}.stderr.log"
rc=$?
set -e
validate=("${validator[@]}" --report "$report" --expect-file "$file")
for finding in "$@"; do
validate+=(--expect-finding "$finding")
done
if [[ "$expected" == pass ]]; then
(( rc == 0 )) || return 1
"${validate[@]}" --require-pass
else
(( rc != 0 )) || return 1
"${validate[@]}"
fi
}
run_case pass testdata/clean.tf.json
run_case fail testdata/open-cidr.tf.json 'globally open CIDR'
run_case fail testdata/unresolved-cidr.tf.json 'literal cidr_blocks'
run_case fail testdata/public-ip.tf.json 'literal false'
run_case fail testdata/missing-resource.tf.json 'Terraform JSON must contain a non-empty resource object'
run_case fail testdata/non-object-resource.tf.json 'Terraform JSON must contain a non-empty resource object'
run_case fail testdata/malformed-resource-maps.tf.json 'resource.aws_security_group must be an object' 'resource.aws_instance must be an object'
run_case fail testdata/malformed-resource-rows.tf.json 'aws_security_group.bad_group must be an object' 'ingress[0] must be an object' 'aws_instance.bad_instance must be an object'
set +e
"${conftest[@]}" "${declared_sorted[@]}" >conftest-report.json 2>conftest-stderr.log
gate_rc=$?
set -e
(( gate_rc == 0 )) || exit "$gate_rc"
validate_production=("${validator[@]}" --report conftest-report.json --require-pass)
for path in "${declared_sorted[@]}"; do
validate_production+=(--expect-file "$path")
done
"${validate_production[@]}"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
if: ${{ always() }}
with:
name: conftest-report
path: |
conftest-report.json
conftest-stderr.log
fixture-reports/
if-no-files-found: error
The custom package is selected explicitly with --namespace aidefend.baseline. The shell rejects an empty, duplicate, unsafe, missing, or extra declared input population and uses set -euo pipefail. Independent report parsing requires a non-empty result, exact filenames and namespace, positive successful evaluations for every passing file, no warnings, exceptions, or findings, and every expected negative finding; a missing policy, zero-test result, parser error, or incomplete population cannot become a pass. Reports upload even when the gate fails.
AID-M-005.002-G002
Concept
Quantify compliance (coverage, violations, exceptions) and make them enforceable gates at pre-merge and pre-release.
SLO spec
# File: governance/baseline_slo.yaml
slo:
schema_version: aidefend.baseline-slo.v1
coverage_target_percent: 100
violations:
critical: 0
high: 0
medium_max: 3
exceptions:
approval_authority: PlatformSec
max_ttl_days: 30
report_public_key: keys/policy-report.pub
signature_bundle: out/policy_report.sig
verification_timeout_seconds: 30
CI check (Python helper)
# File: tools/check_slo.py
from __future__ import annotations
import json
import math
import subprocess
import tempfile
from datetime import datetime, timezone
from pathlib import Path
import yaml
SLO_PATH = Path("governance/baseline_slo.yaml")
REPORT_PATH = Path("out/policy_report.json")
def unique_object(pairs):
value = {}
for key, item in pairs:
if key in value:
raise ValueError(f"duplicate JSON key: {key}")
value[key] = item
return value
def when(value: str) -> datetime:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
if parsed.tzinfo is None:
raise ValueError("exception timestamp lacks timezone")
return parsed.astimezone(timezone.utc)
def verified_report_bytes(slo: dict) -> bytes:
payload = REPORT_PATH.read_bytes()
bundle = Path(slo["signature_bundle"]).read_bytes()
if not payload or not bundle:
raise ValueError("policy report or signature bundle is empty")
with tempfile.TemporaryDirectory(prefix="aidefend-slo-") as stage_name:
stage = Path(stage_name)
snapshot = stage / "policy_report.json"
signature = stage / "policy_report.sig"
snapshot.write_bytes(payload)
signature.write_bytes(bundle)
subprocess.run(
[
"cosign", "verify-blob", "--key", slo["report_public_key"],
"--bundle", str(signature), str(snapshot),
],
check=True, capture_output=True, text=True,
timeout=slo["verification_timeout_seconds"],
)
if snapshot.read_bytes() != payload or signature.read_bytes() != bundle:
raise ValueError("verified report snapshot changed")
return payload
def fail(message: str) -> None:
raise SystemExit("SLO BLOCKED: " + message)
config = yaml.safe_load(SLO_PATH.read_text(encoding="utf-8"))
if not isinstance(config, dict) or set(config) != {"slo"}:
fail("SLO document schema differs")
slo = config["slo"]
required_slo = {
"schema_version", "coverage_target_percent", "violations", "exceptions",
"report_public_key", "signature_bundle", "verification_timeout_seconds",
}
if not isinstance(slo, dict) or set(slo) != required_slo:
fail("SLO policy schema differs")
if slo["schema_version"] != "aidefend.baseline-slo.v1":
fail("SLO policy version differs")
try:
report = json.loads(
verified_report_bytes(slo).decode("utf-8", errors="strict"),
object_pairs_hook=unique_object,
parse_constant=lambda value: (_ for _ in ()).throw(
ValueError(f"non-finite JSON number: {value}")
),
)
except (OSError, UnicodeError, ValueError, subprocess.SubprocessError) as error:
fail("report verification error: " + str(error))
if not isinstance(report, dict) or set(report) != {
"schema_version", "coverage_percent", "violations", "exceptions",
} or report["schema_version"] != "aidefend.policy-report.v1":
fail("policy report schema differs")
coverage = report["coverage_percent"]
if (
isinstance(coverage, bool) or not isinstance(coverage, (int, float))
or not math.isfinite(coverage) or not 0 <= coverage <= 100
or coverage < float(slo["coverage_target_percent"])
):
fail("coverage target is not met")
now = datetime.now(timezone.utc)
valid_exception_ids = set()
for exception in report["exceptions"]:
if not isinstance(exception, dict) or set(exception) != {
"id", "owner", "approved_by", "approved_at", "expires_at",
}:
fail("exception schema differs")
approved = when(exception["approved_at"])
expires = when(exception["expires_at"])
ttl = (expires - approved).total_seconds() / 86400
if (
not exception["id"] or exception["id"] in valid_exception_ids
or not exception["owner"]
or exception["approved_by"] != slo["exceptions"]["approval_authority"]
or approved > now or expires <= now
or ttl <= 0 or ttl > float(slo["exceptions"]["max_ttl_days"])
):
fail("exception is unapproved, expired, duplicated, or over TTL")
valid_exception_ids.add(exception["id"])
effective = []
violation_ids = set()
for violation in report["violations"]:
if not isinstance(violation, dict) or set(violation) != {
"id", "severity", "exception_id",
}:
fail("violation schema differs")
if (
not violation["id"] or violation["id"] in violation_ids
or violation["severity"] not in {"critical", "high", "medium", "low"}
):
fail("violation identity or severity is invalid")
violation_ids.add(violation["id"])
exception_id = violation["exception_id"]
if exception_id is not None and exception_id not in valid_exception_ids:
fail("violation references an invalid exception")
if exception_id is None:
effective.append(violation)
counts = {
severity: sum(item["severity"] == severity for item in effective)
for severity in ("critical", "high", "medium")
}
limits = slo["violations"]
if counts["critical"] != limits["critical"]:
fail("critical violation threshold exceeded")
if counts["high"] != limits["high"]:
fail("high violation threshold exceeded")
if counts["medium"] > limits["medium_max"]:
fail("medium violation threshold exceeded")
print("SLO OK")
Action: Sign the complete report after generation, then run both python tools/check_slo.py and python -O tools/check_slo.py in CI. Both invocations must produce the same result. Signature failures, malformed reports, incomplete coverage, and invalid exceptions are blocking errors; approved exceptions must carry an owner, approval authority, and bounded expiry.
AID-M-005.002-G003
Concept
Generate the baseline manifest from verified report bytes; do not hand-type hashes, violation counts, or exception dates. Scanner-specific adapters first normalize their output and the ruleset into one signed release-input document. This guidance owns the aggregate manifest and its binding, not the scanners' internal verification.
Build from the signed release-input population
The release-input schema contains baseline_version, build_digest, control_mappings, one ruleset entry, a policy-owned non-empty required_report_kinds population, exactly one report for each required kind, and approved exceptions. Every file entry has a regular path and lowercase sha256. Each normalized report has schema aidefend.posture-report.v1, the same build digest, and exact critical/high/medium/low violation counts.
# File: governance/build_baseline_manifest.py
from __future__ import annotations
import hashlib
import json
import os
import re
from datetime import datetime, timezone
from pathlib import Path
SHA256 = re.compile(r"^[0-9a-f]{64}$")
SEVERITIES = ("critical", "high", "medium", "low")
def unique_object(pairs):
value = {}
for key, item in pairs:
if key in value:
raise ValueError(f"duplicate JSON key: {key}")
value[key] = item
return value
def regular_bytes(path: Path) -> bytes:
if path.is_symlink() or not path.is_file():
raise ValueError(f"baseline input is not a regular file: {path}")
raw = path.read_bytes()
if not raw:
raise ValueError(f"baseline input is empty: {path}")
return raw
def checked_entry(entry: dict, *, expected_keys: set[str]) -> tuple[dict, bytes]:
if not isinstance(entry, dict) or set(entry) != expected_keys:
raise ValueError("baseline file-entry schema differs")
if not SHA256.fullmatch(str(entry["sha256"])):
raise ValueError("baseline file-entry digest is invalid")
raw = regular_bytes(Path(entry["path"]))
if hashlib.sha256(raw).hexdigest() != entry["sha256"]:
raise ValueError(f"baseline file-entry digest differs: {entry['path']}")
return entry, raw
source_path = Path(os.environ["BASELINE_RELEASE_INPUTS"])
source_raw = regular_bytes(source_path)
source = json.loads(
source_raw.decode("utf-8", errors="strict"),
object_pairs_hook=unique_object,
)
required = {
"schema_version", "baseline_version", "build_digest",
"control_mappings", "ruleset", "required_report_kinds",
"reports", "exceptions",
}
if not isinstance(source, dict) or set(source) != required:
raise ValueError("baseline release-input fields differ")
if source["schema_version"] != "aidefend.baseline-release-inputs.v1":
raise ValueError("baseline release-input schema differs")
if not SHA256.fullmatch(str(source["build_digest"])):
raise ValueError("build digest is invalid")
if not isinstance(source["control_mappings"], dict) or not source["control_mappings"]:
raise ValueError("control mapping population is empty")
required_report_kinds = source["required_report_kinds"]
if (
not isinstance(required_report_kinds, list)
or not required_report_kinds
or any(
not isinstance(kind, str) or not kind.strip()
for kind in required_report_kinds
)
or len(required_report_kinds) != len(set(required_report_kinds))
):
raise ValueError("required report-kind population is invalid")
ruleset, _ruleset_raw = checked_entry(
source["ruleset"],
expected_keys={"kind", "path", "sha256", "policy_version"},
)
if ruleset["kind"] != "ruleset_manifest" or not ruleset["policy_version"]:
raise ValueError("ruleset binding is invalid")
reports = source["reports"]
if not isinstance(reports, list) or not reports:
raise ValueError("report population is empty")
totals = {severity: 0 for severity in SEVERITIES}
bound_reports = []
seen_report_kinds = set()
for entry in reports:
entry, raw = checked_entry(
entry,
expected_keys={"kind", "path", "sha256", "adapter_version"},
)
if (
not isinstance(entry["kind"], str)
or not entry["kind"].strip()
or entry["kind"] in seen_report_kinds
):
raise ValueError("report kind is empty or duplicated")
seen_report_kinds.add(entry["kind"])
report = json.loads(raw.decode("utf-8", errors="strict"), object_pairs_hook=unique_object)
if set(report) != {"schema_version", "build_digest", "violations"}:
raise ValueError(f"normalized posture report schema differs: {entry['kind']}")
if (
report["schema_version"] != "aidefend.posture-report.v1"
or report["build_digest"] != source["build_digest"]
or not isinstance(report["violations"], dict)
or set(report["violations"]) != set(SEVERITIES)
):
raise ValueError(f"normalized posture report binding differs: {entry['kind']}")
for severity in SEVERITIES:
count = report["violations"][severity]
if isinstance(count, bool) or not isinstance(count, int) or count < 0:
raise ValueError(f"invalid violation count: {entry['kind']}/{severity}")
totals[severity] += count
bound_reports.append(entry)
if seen_report_kinds != set(required_report_kinds):
missing = sorted(set(required_report_kinds) - seen_report_kinds)
unexpected = sorted(seen_report_kinds - set(required_report_kinds))
raise ValueError(
f"report-kind population differs; missing={missing}, unexpected={unexpected}"
)
report_population_sha256 = hashlib.sha256(json.dumps(
sorted(seen_report_kinds),
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")).hexdigest()
now = datetime.now(timezone.utc)
exceptions = source["exceptions"]
if not isinstance(exceptions, list):
raise ValueError("exception population is invalid")
seen_exception_ids = set()
for exception in exceptions:
if not isinstance(exception, dict) or set(exception) != {
"id", "rule", "owner", "expires_at", "approval_sha256",
}:
raise ValueError("exception schema differs")
if (
any(not isinstance(exception[key], str) or not exception[key] for key in ("id", "rule", "owner"))
or exception["id"] in seen_exception_ids
or not SHA256.fullmatch(str(exception["approval_sha256"]))
):
raise ValueError("exception identity or approval binding is invalid")
seen_exception_ids.add(exception["id"])
expiry = datetime.fromisoformat(exception["expires_at"].replace("Z", "+00:00"))
if expiry.tzinfo is None or expiry <= now:
raise ValueError(f"exception is expired: {exception['id']}")
manifest = {
"schema_version": "aidefend.baseline-manifest.v1",
"baseline_version": source["baseline_version"],
"build_digest": source["build_digest"],
"release_inputs_sha256": hashlib.sha256(source_raw).hexdigest(),
"ruleset": ruleset,
"control_mappings": source["control_mappings"],
"required_report_kinds": required_report_kinds,
"report_population_sha256": report_population_sha256,
"violations": totals,
"exceptions": exceptions,
"reports": bound_reports,
}
output = Path("out/baseline_manifest.json")
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
if json.loads(output.read_text(encoding="utf-8")) != manifest:
raise RuntimeError("baseline manifest readback differs")
Verify the input snapshot, sign the output, and replay every digest
# File: governance/publish_baseline_manifest.sh
#!/usr/bin/env bash
set -euo pipefail
umask 077
snapshot_root="$(mktemp -d)"
trap 'rm -rf -- "$snapshot_root"' EXIT
install -m 0400 -- "$BASELINE_RELEASE_INPUTS" "$snapshot_root/release-inputs.json"
install -m 0400 -- "$BASELINE_RELEASE_INPUTS_BUNDLE" "$snapshot_root/release-inputs.sigstore.json"
cosign verify-blob --key "$BASELINE_RELEASE_INPUTS_VERIFY_KEY" --bundle "$snapshot_root/release-inputs.sigstore.json" "$snapshot_root/release-inputs.json" >/dev/null
export BASELINE_RELEASE_INPUTS="$snapshot_root/release-inputs.json"
python governance/build_baseline_manifest.py
cosign sign-blob --yes --key "$BASELINE_MANIFEST_SIGNING_KEY" --bundle out/baseline_manifest.sigstore.json out/baseline_manifest.json
cosign verify-blob --key "$BASELINE_MANIFEST_VERIFY_KEY" --bundle out/baseline_manifest.sigstore.json out/baseline_manifest.json >/dev/null
jq -e '.schema_version == "aidefend.baseline-manifest.v1"' out/baseline_manifest.json >/dev/null
while IFS=$'\t' read -r path expected; do
observed="$(sha256sum -- "$path" | awk '{print $1}')"
[[ "$observed" == "$expected" ]] || exit 1
done < <(jq -r '
[.ruleset, .reports[]] | .[] | [.path, .sha256] | @tsv
' out/baseline_manifest.json)
Action: Promotion must reconcile report kinds exactly against the policy-owned required population and reject an expired exception, missing, unexpected, or duplicate report, report/build mismatch, stale ruleset, digest mismatch, invalid signature, or incomplete replay. Store the verified manifest and bundle under the release's immutable build digest.
Open-source tools
- Checkov
- Trivy
- KICS
- Conftest
- Open Policy Agent (OPA)
- TruffleHog
- Gitleaks
- git-secrets
Commercial / hosted tools
- Snyk Infrastructure as Code
- Palo Alto Networks Prisma Cloud Application Security
- Wiz Cloud Security Platform
- Tenable Cloud Security
Threat mappings
MITRE ATLAS
- AML.T0011 User Execution
- AML.T0010 AI Supply Chain Compromise
- AML.T0010.001 AI Supply Chain Compromise: AI Software
- AML.T0010.004 AI Supply Chain Compromise: Container Registry
- AML.T0011.001 User Execution: Malicious Package
- AML.T0055 Unsecured Credentials
- AML.T0081 Modify AI Agent Configuration
- AML.T0083 Credentials from AI Agent Configuration
MAESTRO
- Infrastructure-as-Code (IaC) Manipulation (L4)
- Orchestration Attacks (L4)
- Compromised Container Images (L4)
OWASP LLM Top 10 2026
- N/A
OWASP ML Top 10 2023
- ML06:2023 AI Supply Chain Attacks
- ML05:2023 Model Theft (storage and access-control release gates block insecure model exposure)
OWASP Top 10 for Agentic Applications 2026
- ASI04:2026 Agentic Supply Chain Vulnerabilities (policy-as-code release gates reject unapproved component configuration)
- ASI03:2026 Identity and Privilege Abuse (access-control baselines gate insecure identity configuration)
NIST Adversarial Machine Learning 2025
- NISTAML.039 Compromising connected resources
- NISTAML.051 Model Poisoning (Supply Chain) (signed policy baselines gate unapproved software and deployment configuration)
Cisco Integrated AI Security and Safety Framework
- AISubtech-14.1.2 Insufficient Access Controls (Blocks AI-host releases with measured public-exposure configuration violations)
- AITech-14.1 Unauthorized Access (Blocks AI-host releases with measured public-exposure configuration violations)
Google Secure AI Framework 2.0 - Risks
- MST: Model Source Tampering (policy-as-code CI gates block tampered/non-compliant IaC before merge)
- MDT: Model Deployment Tampering (signed baseline manifests and promotion verification prevent deployment-time tampering)
- MXF: Model Exfiltration (posture SLOs enforce secure storage configurations blocking exfiltration paths)
- IIC: Insecure Integrated Component (IaC scanners and policy checks catch insecure dependency/integration configs)
Databricks AI Security Framework 3.0
- Algorithms 5.4: Malicious libraries
- Model 7.3: ML Supply chain vulnerabilities
- Model 7.4: Source code control attack
- Platform 12.1: Lack of vulnerability management
- Platform 12.5: Poor security in the software development lifecycle
- Governance 4.1: Lack of traceability and transparency of model assets
- Operations 11.1: Lack of MLOps - repeatable enforced standards
- Agents - Tools MCP Server 13.20: Insecure Server Configuration