This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/security-review/checkers/fixtures/aws-posture
Adam Moussa a9bccf33d0 feat(secrev): aws-posture checker (Tier-2, provisioning-gated)
Read-only Tier-2 idle/anomalous-spend + idle-resource posture checker for the
R720 agent-team (design D5 / §4 / §6.3 / §7 Phase 3). Mirrors the Tier-1 checker
conventions verbatim (flags --canary/--dry-run/--no-api/--targets, mode-600
report under $REPORT_ROOT/aws-posture/<date>/, ALARM-only, finding.schema.json
spirit, exit 0/2/3, shared substrate redact/post_slack_alarm).

Detectors (complement GuardDuty/SecurityHub/Config, do not replace):
- anomalous Cost Explorer deltas (ce get-anomalies, $-impact threshold)
- stopped EC2 still paying for attached EBS
- unattached EBS volumes
- unassociated Elastic IPs
- idle NAT gateways (≈0 bytes out)
- idle load balancers (0 healthy targets)
- idle RDS (0 connections over window)

Live AWS calls are PROVISIONING-GATED: they run ONLY when Roles Anywhere creds
are available (STS identity probe) AND not --no-api/--canary. With no creds or
--no-api/--canary the checker SKIPS live calls and notes them — NEVER alarms on
missing data (memory feedback_cloudwatch_alarms). Roles Anywhere/step-ca are not
stood up (IAM cross-review PASSED 2026-06-18; see security-review/iam/).

Offline canary: fixtures of mocked AWS responses (cost/describe-* JSON) under
fixtures/aws-posture/ + EXPECTED_FINDING_COUNT=7, asserted fully offline (no aws,
no network). Identical detector code runs online and offline. shellcheck-clean
(only accepted SC1091), chmod +x.
2026-06-18 16:18:11 -04:00
..
cost-anomalies.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
describe-addresses.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
describe-db-instances.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
describe-instances.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
describe-load-balancers.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
describe-nat-gateways.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
describe-volumes.json feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
EXPECTED_FINDING_COUNT feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00
README.md feat(secrev): aws-posture checker (Tier-2, provisioning-gated) 2026-06-18 16:18:11 -04:00

aws-posture canary fixtures

Mocked AWS API responses for checkers/aws-posture.sh --canary (offline — no aws calls, no network, no credentials). The canary feeds these files to the SAME detectors the live path runs against real aws CLI output, and asserts the total finding count equals EXPECTED_FINDING_COUNT (anti-complacency floor, design §6.4). If a detector regresses (stops firing), the count drops and the canary FAILS (exit 3).

These are plain JSON files (not git fixtures — aws-posture scans an AWS account, not a repo tree), so there is no dotgit/ / .fixture rename trick here; the offline-vs-live seam is the --canary/--no-api/no-credentials guard inside the checker (mirrors compliance-drift's API-skip pattern). Each file is shaped like the real aws ... --output json response it stands in for; a few _Fixture* helper keys carry the per-resource metric the live path derives from CloudWatch (so the canary stays deterministic and offline).

Fixture file Stands in for Planted finding Count
cost-anomalies.json aws ce get-anomalies 1 anomaly TotalImpact ≥ threshold (the other is below threshold → must NOT fire) 1
describe-instances.json aws ec2 describe-instances 1 stopped instance still paying for its EBS root (the running one must NOT fire) 1
describe-volumes.json aws ec2 describe-volumes 1 available (unattached) volume (the in-use one must NOT fire) 1
describe-addresses.json aws ec2 describe-addresses 1 EIP with no association (the associated one must NOT fire) 1
describe-nat-gateways.json aws ec2 describe-nat-gateways 1 available NAT with ~0 bytes out / 14d (the busy one must NOT fire) 1
describe-load-balancers.json aws elbv2 describe-load-balancers 1 ALB with 0 healthy targets (the one with 3 must NOT fire) 1
describe-db-instances.json aws rds describe-db-instances 1 available RDS with 0 connections / 14d (the busy one must NOT fire) 1

Total = 7 (EXPECTED_FINDING_COUNT).

aws-posture complements GuardDuty / Security Hub / Config (design §4 / Tier-2) — it is an idle/anomalous-spend + idle-resource posture watch, not a threat detector, and never alarms on missing data (a skipped/credential-less live call is noted, never counted — memory feedback_cloudwatch_alarms).

When you add/remove a detector or fixture, update both the fixture and EXPECTED_FINDING_COUNT in the same commit (the canary edit is itself caught on the next run — design §6.4).