Skip to content

Repository files navigation

OverWatch — Agentless AWS CNAPP

OverWatch — an agentless AWS CNAPP: it collapses a whole cloud estate into the ranked handful of internet → exposed workload → exploitable CVE → over-privileged role → crown-jewel attack paths and names the choke point that severs the most — plus an IaC static-analysis scanner.

Python 3.10+ GPL-3.0 License OverWatch CNAPP v3.0.0 CNAPP pillars 5 Compliance Frameworks 296 severity-mapped checks 5458 Tests


Table of Contents


Overview

OverWatch is the product name for the CNAPP: a full Cloud-Native Application Protection Platform for AWS (CSPM + CIEM + agentless CWPP + DSPM + AI-SPM + CDR) built around a unified security graph and toxic-combination attack-path correlation, with multi-account onboarding, choke-point remediation, and code-to-cloud mapping. Beyond posture it adds an AI-SPM pillar (the blast radius of an AI resource's execution role, fused into the graph), CDR-lite streaming detection ingest (GuardDuty / Security Hub / CloudTrail-anomaly events → ranked incidents), a cloud-forensics timeline, and a grounded-RAG copilot that answers only from an account's own scan. It ships as the live scanner (aws_live_scanner.py) + its aws_* / cnapp_* engine modules, and a hosted platform backend (see OverWatch — Hosted CNAPP Platform).

This repository contains two complementary AWS security scanners:

Scanner File Type Input Checks
OverWatch (Live CNAPP) aws_live_scanner.py Live AWS API audit + security graph + attack-path CNAPP Running AWS account (multi-account via AssumeRole) 296 severity-mapped across 44 sections
IaC Security Scanner aws_offline_scanner.py Static analysis CloudFormation + Terraform files 100+ (60+ TF regex + 42 CF structural)

Use OverWatch to audit a running AWS estate — CIS/compliance posture, effective-permissions CIEM, agentless workload vulnerabilities, and the ranked attack paths + choke points. Use the IaC scanner to catch misconfigurations in CloudFormation templates and Terraform files before deployment.


Architecture at a glance

OverWatch is a three-tier system — a React console over a fail-closed control plane over a pure, agentless scanning engine — that turns read-only AWS reads into a ranked handful of attack paths.

OverWatch logical architecture

Logical architecture: React console → fail-closed control plane → pure scanning engine → read-only sources.

The differentiator — score the path, not the finding. A toxic combination is a conjunction (exposure × exploitability × privilege × data-path), so a missing factor collapses the score and the classic "high-CVSS but unexposed, no data path" false positive never surfaces.

The attack-path graph

An end-to-end attack path over traversable (solid) E_PATH edges; annotations (dashed) enrich nodes but are never walked as hops.

Runtime topology. One hardened, non-root container on a private subnet in the security VPC — reaching AWS via VPC endpoints and assuming read-only roles into each spoke account. No public-internet egress.

Runtime topology

Hub-and-spoke: VPC-endpoint egress + sts:AssumeRole (with ExternalId) into spoke accounts.

The scan pipeline. Read-only collect → graph → reachability → deep-plane → correlate; every specialized posture engine (eff-perm, CIEM, CWPP, KSPM/KIEM, DSPM, AI-SPM, CDR/EDR/malware ingest) reads the same graph.

Scan pipeline and engine fan-out

The pipeline and engine fan-out — annotations enrich; only E_PATH edges are traversed.

Zero-telemetry by construction. The overwhelming majority of egress is read-only boto3 to the customer's own accounts; only three allowlisted files may open any other socket, enforced by a recursive-AST tripwire test.

Egress containment

Egress containment — internet / telemetry / phone-home is blocked.

Full detail: the System Design & Architecture guide (Word — 21 chapters, 13 tables, and these five figures) is regenerable, offline, from docs/diagrams/.


IaC Security Scanner (aws_offline_scanner.py)

What It Does

The IaC scanner performs pure static analysis of AWS Infrastructure-as-Code files -- no AWS credentials required. It scans CloudFormation templates (YAML/JSON) and Terraform configuration files (.tf) for security misconfigurations mapped to the CIS AWS Benchmark and AWS Well-Architected Security Pillar.

  • No AWS credentials required -- analyses files locally
  • 100+ security checks -- 60+ Terraform regex rules + 42 CloudFormation structural checks
  • 25+ AWS services covered -- S3, IAM, EC2, RDS, Lambda, CloudTrail, and more
  • 3 output formats -- coloured console, JSON, interactive HTML
  • Optional dependency -- pyyaml for CloudFormation YAML files (JSON works without it)

Quick Start (IaC Scanner)

# Scan a directory of IaC files
python -m engine.aws_offline_scanner /path/to/infra/

# Scan a single CloudFormation template
python -m engine.aws_offline_scanner template.yaml --html report.html

# Scan Terraform files with severity filter
python -m engine.aws_offline_scanner main.tf --json findings.json --severity HIGH

# Verbose mode
python -m engine.aws_offline_scanner /path/to/cf/ --verbose --severity MEDIUM

CLI Reference (IaC Scanner)

usage: aws_offline_scanner.py [-h] [--json FILE] [--html FILE]
                               [--severity {CRITICAL,HIGH,MEDIUM,LOW,INFO}]
                               [-v] [--version]
                               target

positional arguments:
  target                File or directory containing CloudFormation templates or Terraform files

options:
  --json FILE           Write JSON report to FILE
  --html FILE           Write HTML report to FILE
  --severity SEV        Only report findings at this severity or above
  -v, --verbose         Show files as they are scanned
  --version             Show scanner version

Terraform SAST Rules (60+ Rules)

Service Rule IDs Count Key Checks
S3 AWS-S3-TF-001 to 006 6 Public ACLs, Block Public Access settings
IAM AWS-IAM-TF-001 to 004 4 Wildcard actions/principals, password reset
EC2/SG AWS-SG-TF-001 to 003, AWS-EC2-TF-001 to 003 6 SSH/RDP from 0.0.0.0/0, IMDSv1, public IP, EBS encryption
RDS AWS-RDS-TF-001 to 006 6 Public access, encryption, backups, deletion protection, Multi-AZ
CloudTrail AWS-CT-TF-001 to 003 3 Log validation, multi-region, global events
KMS AWS-KMS-TF-001 1 Key rotation
CloudFront AWS-CF-TF-001 to 002 2 HTTPS enforcement, TLS version
ElastiCache AWS-ECACHE-TF-001 to 002 2 At-rest and in-transit encryption
ECS AWS-ECS-TF-001 to 002 2 Privileged mode, writable root filesystem
OpenSearch AWS-OS-TF-001 to 002 2 HTTPS enforcement, node-to-node encryption
Redshift AWS-RS-TF-001 to 002 2 Public access, encryption
ECR AWS-ECR-TF-001 1 Mutable image tags
DynamoDB AWS-DDB-TF-001 1 SSE encryption
Lambda AWS-LAM-TF-001 1 Reserved concurrency throttling
API Gateway AWS-APIGW-TF-001 1 Stage logging
Credentials AWS-CRED-TF-001 to 002 2 Hardcoded AWS keys, passwords
CloudWatch AWS-CW-TF-001 to 003 3 Log retention, KMS encryption, alarm actions
VPC AWS-VPC-TF-001 to 002 2 Flow logs, public subnet auto-assign
WAF AWS-WAF-TF-001 1 Default allow action
GuardDuty AWS-GD-TF-001 1 Detector disabled
Config AWS-CFG-TF-001 to 002 2 All resource types, global resources
Elastic Beanstalk AWS-EB-TF-001 to 002 2 HTTPS listener, managed updates
SageMaker AWS-SM-TF-001 to 002 2 Internet access, storage encryption
EBS AWS-EBS-TF-001 1 Volume encryption
Step Functions AWS-SFN-TF-001 to 002 2 Logging, X-Ray tracing
Bedrock AWS-BR-TF-001 1 Guardrails

CloudFormation Structural Checks (42 Resource Types)

Service Resource Types Key Checks
IAM Role, Policy, ManagedPolicy, User Wildcard principal/action, AdministratorAccess, inline user policies
S3 Bucket Public access block, versioning, encryption, logging
EC2 SecurityGroup, Instance Open ports (22/3389/0-65535), IMDSv2, public IP
RDS DBInstance, DBCluster Public access, encryption, backups, deletion protection, Multi-AZ
Lambda Function Reserved concurrency, KMS encryption, tracing
CloudTrail Trail Log validation, multi-region, S3 encryption
CloudFront Distribution HTTPS, TLS 1.2+, WAF, logging, origin protocol
ELB Listener HTTPS protocol enforcement
API Gateway Stage Logging, stage variables
KMS Key Rotation enabled
SQS Queue KMS encryption
SNS Topic KMS encryption
DynamoDB Table Point-in-time recovery, SSE
ElastiCache ReplicationGroup Encryption (rest + transit), auth token
EKS Cluster Endpoint public access, logging, encryption
ECS TaskDefinition Privileged mode, read-only root, logging
Cognito UserPool MFA, password policy
OpenSearch Domain HTTPS, node-to-node encryption, encryption at rest
Redshift Cluster Public access, encryption, logging
ECR Repository Image tag mutability, scan on push
Secrets Manager Secret KMS encryption
CloudWatch Alarm Alarm actions
Logs LogGroup Retention, KMS encryption
VPC/Subnet VPC, Subnet, FlowLog Flow logs, public IP auto-assign
WAFv2 WebACL Default action
GuardDuty Detector Enabled status
Config ConfigurationRecorder All supported types, global resources
Elastic Beanstalk Environment HTTPS, managed updates
SageMaker NotebookInstance, Domain Internet access, KMS encryption
Bedrock Agent Guardrails
EBS Volume Encryption
Step Functions StateMachine Logging, tracing

Live Audit Scanner (aws_live_scanner.py)

What It Does

The live scanner connects to a running AWS account via boto3, performing read-only security checks aligned to multiple compliance frameworks. It produces colour-coded terminal output with PASS/FAIL/WARN verdicts, posture scoring, JSON/HTML reports, and saves evidence artefacts to a timestamped output directory.

  • Read-only by design -- never modifies AWS resources
  • 296 severity-mapped checks across 44 audit sections (222 actionable, each with a detailed remediation write-up)
  • Effective internet-exposure engine (CNAPP Phase 2) -- computes true reachability (aws_exposure.py): a workload is flagged internet-exposed only when a public IP/EIP/IPv6 and an active IGW route and open security-group ingress and a permissive stateless NACL (inbound + ephemeral return) all line up — killing the "SG allows 0.0.0.0/0" false positive. sg-references, NAT routes, private subnets and blocked-return NACLs are correctly not exposed
  • First attack path -- ATTACK-01 chains it end-to-end: Internet → exposed EC2 → instance-profile role → privilege escalation to admin (CRITICAL)
  • Network micro-segmentation (Phase-3) -- Layer A (SEG-01..06, always-on, config-only, zero new grant) flags over-permissive security groups (world-open sensitive ports, wide ranges, unused SGs, SG-chains to a world-open group, unrestricted egress) as a distinct lens from the 4-gate reachability oracle — a config-level SG hole is caught even when the host is not currently reachable. Layer B (FLOW-01..03, opt-in --flow-logs) reads VPC Flow-Log observed traffic via CloudWatch Logs Insights to recommend evidence-based scope-downs ("only these /24s ever connected"), flag allowed-but-never-used ports, and surface top reject/recon talkers — annotating the graph's EXPOSED_TO edges with observed evidence (kept out of the traversable attack-path set so the low-FP reachability guarantee is preserved), and failing open to a FLOW-00 note when the optional resource-scoped logs:StartQuery grant is absent
  • Deep-plane ingestion (CNAPP Phase 3, buy-not-build) -- BUYS signal from AWS-native services as graph edges: Amazon Inspector CVEs with EPSS + CISA-KEV (HAS_VULN), Macie crown-jewel S3 classification, IAM Access Analyzer authoritative external access, GuardDuty live detections (THREAT_ON). Every collector degrades to a graceful no-op when its service is disabled -- never a false positive
  • Flagship attack path -- ATTACK-02 (CRITICAL): Internet → exposed EC2 → exploitable/KEV CVE → over-privileged role → crown-jewel S3 data -- the full toxic combination, condition-aware, escalated when a live GuardDuty threat sits on the chain
  • Attack-path correlation & choke points (CNAPP Phase 4) -- collapses the whole graph into a ranked handful of scored, explainable attack paths (gated-multiplicative scoring: any missing factor collapses the path, killing the "high-CVSS but unexposed" false positive), then computes choke points: CHOKEPOINT-01 names the single node whose fix severs the most paths to the most crown jewels. Ranked attack_paths + choke_points in the JSON report
  • Effective-permissions ceiling (CNAPP Phase 5) -- evaluates the real AWS decision chain (identity ∩ permission-boundarySCP, explicit-deny-wins, condition-aware) so an escalation edge a boundary or SCP provably neutralizes is dropped from the graph — the ranked paths reflect genuinely-reachable escalation, not merely granted permissions. Fail-open: absent boundary/SCP data behaves exactly as before (aws_effperm.py)
  • Persistent state, drift & waivers (CNAPP Phase 5) -- --state cnapp.db gives the scanner memory: finding lifecycle (open/resolved/reopened/mutated), coverage-gated resolve, MTTR, posture trend, and waivers (--suppress with approver/expiry — an accepted risk leaves --fail-on gating but stays tracked; expired waivers auto-reactivate) (aws_state.py)
  • CIEM right-sizing (CNAPP Phase 5, opt-in --ciem) -- flags dormant/over-permissioned principals (Access Analyzer unused-access → service-last-accessed) as LOW CIEM-01 review candidates and down-ranks the exploit-likelihood of attack paths through them (impact untouched) (aws_unused.py)
  • Least-privilege policy generation (opt-in --ciem, CIEM-02) -- for a flagged principal, generates a right-sized IAM policy from GRANTED (GAAD) minus USED (service-last-accessed): drops never-used services, narrows svc:* to the used action set. Deterministic, honest (empty usage is never a deny-all; window stated; never auto-applied), and attached to the least_privilege report side-channel (aws_leastpriv.py)
  • DSPM surfaces + AWS-resident secrets -- crown-jewel data classification spans S3/RDS/Aurora/DocumentDB/Neptune/MemoryDB/FSx/Kinesis/Timestream/OpenSearch (tag-driven, + DSPM-03 public resource-policy), and a crown Secret node (Secrets Manager + SSM SecureStrings) with a CAN_READ_DATA reader edge makes internet → workload → role → secret an attack path (SECRET-05); SSM plaintext-parameter posture is SECRET-01/02. All metadata-only — never a secret-value or data read (aws_deepplane.py, aws_secrets.py)
  • Grounded security copilot (aws_copilot.py) -- a scoped RAG assistant that answers natural-language questions using only the scan's own finding_catalog + attack paths + choke points. Self-contained BM25 retrieval (no embeddings/network); offline extractive answers by default (composed purely from corpus fields, so it can't hallucinate), abstains on off-topic questions, and cites the check/path/choke ids it used. An optional injected LLM seam gets only the retrieved corpus as grounded context. Exposed at POST /accounts/{id}/copilot + /org/copilot (viewer RBAC)
  • Agentless workload side-scan (CNAPP Phase 6, opt-in --side-scan) -- the Wiz/Orca CWPP capability with no agent: inventory a workload's OS packages, match them against a vulnerability feed with ecosystem-correct (dpkg/rpm/apk) version comparison, and find on-disk secrets — adding HAS_VULN edges to the SAME graph so agentless CVEs light up ATTACK-02 even when Amazon Inspector is disabled (CWPP-01/02/03). Pure inventory/matching core + EBS Direct-API block plane; live disk extraction deferred (aws_sidescan.py, aws_sidescan_ebs.py)
  • Persistence backends (CNAPP Phase 6) -- --backend postgresql://... (Postgres state store, deferred to Phase 7 → runs stateless) and --graph-neptune-csv/--graph-neptune-cypher export the security graph as Amazon Neptune bulk-load CSV or idempotent openCypher upserts (aws_state_dialect.py, aws_graph_neptune.py)
  • Remediation engine (CNAPP Phase 7, opt-in --remediate) -- turns the ranked attack paths into a prioritized fix plan ("fix these K choke points to cut N% of critical paths"), reusing the correlation ranking, with remediation-as-code (Terraform/CloudFormation/CLI per finding) and exports (markdown runbook, JSON, GitHub issue, PR body). Read-only — it generates artifacts, never applies changes (aws_remediate.py)
  • Code-to-cloud (CNAPP Phase 7, opt-in --iac-dir) -- maps a live cloud finding back to the IaC resource that created it (Terraform/CloudFormation) via a tiered confidence matcher that never guesses, so remediation targets the source and proposes the IaC diff at file:line (aws_codetocloud.py)
  • Security graph & attack-path chains (CNAPP Phase 1) -- projects findings onto an ARN-keyed graph (aws_graph.py), builds CAN_ASSUME (trust) + CAN_PRIVESC_TO (privesc) edges, and surfaces transitive privilege-escalation chains (user → assume role → escalate to admin) plus roles assumable by any principal. Serialize with --graph graph.json (Neptune migration seed)
  • IAM privilege-escalation analysis -- builds each principal's effective permission set (via GetAccountAuthorizationDetails) and detects known escalation paths with resource-aware scoping; condition-guarded paths are downgraded to WARN, boundary/SCP-neutralized paths dropped
  • Multi-account & multi-region -- --org / --accounts fan out across an AWS Organization via --assume-role; --all-regions sweeps every enabled region for regional sections
  • Compliance scorecard -- --compliance prints a per-framework control pass/fail rollup (CIS/PCI/HIPAA/SOC2/NIST), also embedded in the JSON report
  • 5 compliance frameworks -- CIS AWS v3.0, PCI DSS v4.0, HIPAA, SOC 2, NIST 800-53 Rev 5
  • Risk scoring -- Posture score 0-100 with letter grade (A-F), severity-weighted
  • AWS CLI remediation -- actionable CLI commands for every failed check
  • Detailed finding reports -- every actionable check ships a full write-up (aws_finding_detail.py): the risk (what it is / how it's exploited / why it matters), the business impact, and step-by-step remediation with real AWS CLI, plus the mapped compliance controls. The JSON report carries a deduped, severity-ranked finding_catalog; the HTML report renders per-finding cards (risk -> impact -> numbered fix steps -> frameworks) above the full findings table. A check with no detailed entry falls back to its one-line CLI, so coverage grows without breaking rendering
  • 5 output formats -- coloured console, JSON, interactive HTML, SARIF 2.1.0 (GitHub code scanning), ASFF (AWS Security Hub)
  • CI/CD gating -- --fail-on CRITICAL|HIGH|MEDIUM|LOW for pipeline pass/fail control
  • Scan diff -- --baseline prev.json surfaces only what's new or resolved since a previous run (superseded by --state DB-backed lifecycle when both are given)
  • Evidence collection -- CSV/JSON artefact files saved per check
  • Application scorecards (v2.39.0) -- FR-2 end to end: an application registry, findings attributed to an accountable owner, and a per-application scorecard that states its own coverage, segregates excepted findings rather than hiding them, refuses a peer rank it cannot normalise, and renders every withheld figure with the reason in place of the number
  • Governance layer (v2.38.0) -- ownership attribution, SLA/MTTR, a corrected composite Cloud Risk Score, KRA metrics that report NOT_ESTABLISHED rather than a target they did not establish, a CI/CD guardrail gate with a declared failure mode, and forecasts that refuse to exist before their evidence does. Built from a review of the Phase II SRS; the six specification defects it fixed are recorded in CHANGELOG.md and in four ratifiable documents under docs/
  • 5458 backend tests -- full test suite with mock boto3, no AWS credentials needed (incl. the hosted CNAPP platform and the coverage-close batches — WQL/Controls · policy-as-code · EDR/malware/DSPM ingest · non-AWS OCI registry — alongside exposure, deep-plane, attack-path-scoring, Phase-5 effective-permissions/state/CIEM, Phase-6 side-scan version-comparator/OSV-matching/EBS-block-plane/backend-export, Phase-7 remediation/code-to-cloud false-positive/false-negative catalogs, and the detailed finding write-ups / finding_catalog rendering; a regression test backs every defect the adversarial-verification passes found)

Prerequisites (Live Scanner)

  • Python 3.10+ with boto3 installed (pip install boto3)
  • AWS credentials -- configured via aws configure, environment variables, or IAM role
  • IAM permissions -- the executing identity needs the SecurityAudit AWS-managed policy (read-only)
  • Default region -- eu-west-1; override via --region or AWS_DEFAULT_REGION environment variable

Quick Start (Live Scanner)

# Run full audit (all 44 sections, 296 severity-mapped checks)
python -m engine.aws_live_scanner

# Target a specific region
python -m engine.aws_live_scanner --region us-east-1

# Run specific sections only (comma-separated, single argument)
python -m engine.aws_live_scanner --sections IAM,S3,VPC

# Run only the IAM privilege-escalation path analysis
python -m engine.aws_live_scanner --sections IAMPRIVESC

# Save JSON + HTML reports and evidence artefacts
python -m engine.aws_live_scanner --json report.json --html report.html --output-dir ./audit_output

# CI/CD: emit SARIF for GitHub code scanning and fail the build on HIGH+ findings
python -m engine.aws_live_scanner --sarif results.sarif --fail-on HIGH

# Push findings into AWS Security Hub (ASFF)
python -m engine.aws_live_scanner --asff findings.asff.json
aws securityhub batch-import-findings --findings file://findings.asff.json

# CNAPP: all regions, compliance scorecard, and export the identity security graph
python -m engine.aws_live_scanner --all-regions --compliance --graph graph.json

# Multi-account: scan every account in the Organization via an assumable read-only role
python -m engine.aws_live_scanner --org --assume-role OrganizationAccountAccessRole --json org.json

# Multi-account: scan an explicit account list with an ExternalId
python -m engine.aws_live_scanner --accounts 111122223333,444455556666 --assume-role AuditRole --external-id my-id

# Show only what changed since the last scan
python -m engine.aws_live_scanner --json today.json --baseline yesterday.json

# Network micro-segmentation: Layer-A SG analysis (SEG-01..06) runs automatically inside
# EXPOSURE; add the opt-in Layer-B VPC Flow-Log overlay (FLOW-01..03) for evidence-based
# scope-down. Needs the optional resource-scoped logs:StartQuery grant; fails open otherwise.
python -m engine.aws_live_scanner --sections EXPOSURE --flow-logs

# Verbose mode
python -m engine.aws_live_scanner --verbose

CLI Reference (Live Scanner)

usage: aws_live_scanner.py [-h] [--region REGION] [--json FILE] [--html FILE]
                            [--sarif FILE] [--asff FILE] [--baseline FILE]
                            [--fail-on {CRITICAL,HIGH,MEDIUM,LOW}] [--output-dir DIR]
                            [--sections {IAM,S3,VPC,LOGGING,KMS,EC2,ECR,BACKUP,
                                         RDS,GLACIER,SNS,SQS,CLOUDFRONT,ROUTE53,
                                         BEDROCK,BEDROCK_AGENTS,LAMBDA,EKS,ECS,
                                         SECRETS,WAF,ELASTICACHE,OPENSEARCH,
                                         DYNAMODB,STEPFUNCTIONS,APIGATEWAY,ELB,
                                         EBS,REDSHIFT,EFS,ACM,SAGEMAKER,COGNITO,
                                         APIGATEWAYV2,IAMPRIVESC,EXPOSURE,VULN,THREAT,DATA,CORRELATE}]
                            [--all-regions] [--compliance] [--graph FILE]
                            [--org] [--accounts IDS] [--assume-role ROLE]
                            [--external-id ID] [-v] [--version]

options:
  --region REGION       AWS region to audit (default: eu-west-1)
  --json FILE           Write JSON report to FILE
  --html FILE           Write HTML report to FILE
  --sarif FILE          Write SARIF 2.1.0 findings to FILE (GitHub code scanning)
  --asff FILE           Write ASFF findings to FILE (AWS Security Hub import)
  --baseline FILE       Diff against a previous JSON report (new/resolved)
  --fail-on SEVERITY    Exit 1 only on a FAIL at/above this severity
  --output-dir DIR      Directory for evidence artefact files
  --sections SECTIONS   Run only the named sections (single comma-separated value)
  --all-regions         Sweep every enabled region for regional sections
  --flow-logs           Enable the opt-in VPC Flow-Log observed-traffic overlay
                        (FLOW-01/02/03) in EXPOSURE — needs an optional resource-scoped
                        logs:StartQuery grant; bills per GB scanned; fails open to FLOW-00
  --compliance          Print the per-framework compliance scorecard
  --graph FILE          Write the identity security graph to FILE (graph.json)
  --org                 Scan every ACTIVE account in the AWS Organization (needs --assume-role)
  --accounts IDS        Comma-separated account IDs to scan (needs --assume-role)
  --assume-role ROLE    Role name/ARN to assume in each target account
  --external-id ID      STS ExternalId for the assumed role
  -v, --verbose         Print each check as it runs
  --version             Show scanner version

Security Checks Coverage (296 severity-mapped checks across 44 sections)

Section Check IDs Description
IAM IAM-01/02, IAM-04/05/06/10 Root MFA + access keys, console users without MFA, password policy, stale access keys, IAM Access Analyzer
S3 S3-01, S3-03, S3-05 Account-level Block Public Access, per-bucket public access + ACLs + encryption
VPC VPC-01, VPC-03 to 06 Security groups with risky ports open to 0.0.0.0/0, VPC Flow Logs, NACL admin-port exposure, cross-account peering
Network Micro-Seg SEG-01 to 06 (Layer A), FLOW-01 to 03 (Layer B, opt-in) Static SG micro-segmentation (config-only, zero new grant) — world-open sensitive/non-web ports, overly-wide ranges, redundant/shadowed rules, unused SGs, SG-chaining to a world-open group, unrestricted-egress exfil paths; observed-traffic overlay (--flow-logs) — evidence-based SG scope-down, allowed-but-unused port removal, reject-talker recon signals from VPC Flow Logs via CloudWatch Logs Insights (fail-open when the optional logs:StartQuery grant is absent)
Logging LOG-01/03/04/05 CloudTrail multi-region + validation, AWS Config, GuardDuty, Security Hub
KMS ENC-03 KMS customer-managed key rotation
EC2 EC2-04/05/06 IMDSv2, public IP, EBS volume encryption
ECR CNT-01 ECR scan-on-push
Backup BCK-01 AWS Backup vaults and resource assignments
RDS RDS-01 to 06 Encryption, public access, backups, deletion protection, monitoring, public snapshots
Glacier GLC-01 to 03 Vault access policies, vault lock (WORM), SNS notifications
SNS SNS-01 to 04 SSE-KMS encryption, wildcard principal, HTTPS delivery, cross-account subscriptions
SQS SQS-01 to 04 SSE encryption, public access, DLQ, retention/visibility
CloudFront CFN-01 to 05 HTTPS-only, TLS version, WAF, access logging, origin protocol
Route 53 R53-01 to 05 Query logging, DNSSEC, transfer lock, health checks, DNS firewall
Bedrock BDR-01 to 05 Model logging, guardrails, KMS encryption, VPC endpoint, IAM least privilege
Bedrock Agents AGT-01 to 05 Agent KMS encryption, execution role, KB security, Lambda security, guardrail attached
Lambda LMB-01 to 05 Public access, VPC config, plaintext secrets in env vars, deprecated runtimes, concurrency
EKS EKS-01 to 08 Public API endpoint, control plane logging, secrets encryption, version, security groups, worker-node SSH, EKS-Fargate profile boundary, authentication mode
KSPM KSPM-00 to 07 Agentless CIS-EKS (K8s side) — anonymous RBAC bindings, wildcard/cluster-admin RBAC, default-SA automount, Pod Security Admission, default-deny NetworkPolicy, privileged/host pods (fail-open when the K8s API is unreachable)
KIEM KIEM-01 to 04 K8s identity/entitlement — over-broad AWS→K8s cluster-admin grants (EKS Access Entries), namespace-admin/secret-read, and IRSA / Pod-Identity cross-plane (ServiceAccount → AWS role → admin/crown)
ECS ECS-01 to 08, FARGATE-01/02 Privileged containers, root user, log drivers, plaintext secrets, writable rootfs, host-namespace/hostPath escapes, dangerous caps; running Fargate tasks folded into the attack-path graph + public-task-IP exposure
Secrets Manager SEC-01 to 04 Rotation enabled, rotation frequency, KMS (CMK vs managed), unused secrets
WAF WAF-01 to 04 Web ACL presence, logging, rules count, default action
ElastiCache ELC-01 to 04 Encryption at rest, encryption in transit, AUTH token, auto failover
OpenSearch OSR-01 to 05 HTTPS enforcement, encryption at rest, node-to-node, VPC deployment, fine-grained access
DynamoDB DDB-01 to 04 CMK encryption, point-in-time recovery, billing mode, deletion protection
Step Functions SFN-01 to 03 Execution logging, X-Ray tracing, KMS encryption
API Gateway APIGW-01 to 04 Stage access/execution logging, WAF association, cache data encryption, X-Ray tracing
Load Balancing ELB-01 to 05 Access logging, HTTP→HTTPS redirect, TLS policy strength, deletion protection, drop invalid headers
EBS EBS-01 to 04 Encryption by default, unencrypted volumes, unencrypted snapshots, public snapshots
Redshift RS-01 to 05 Encryption at rest, public access, audit logging, enhanced VPC routing, default admin username
EFS EFS-01 to 03 Encryption at rest, in-transit TLS policy, automatic backups
Certificate Manager ACM-01 to 03 Certificate expiry, key algorithm strength, unused certificates
SageMaker SM-01 to 07 Notebook direct internet access, root access, KMS volume encryption, VPC deployment; Studio-domain public egress + home-EFS CMK; endpoint-config storage CMK
Cognito COG-01 to 04 User-pool MFA enforcement, password policy strength, advanced security (threat protection), deletion protection
API Gateway v2 AGW2-01 to 03 HTTP API stage access logging, route authorization, default throttling
IAM Privilege Escalation IAMPE-01 to 20 Resource-aware escalation-path analysis across all principals (findings scoped account-wide vs resource-scoped): policy-version/attach/inline-policy abuse, login-profile & access-key hijack, trust-policy edits, PassRole→(EC2/Lambda/Glue/CFN/SageMaker), UpdateFunctionCode, SSM, sts:AssumeRole-on-*, full-admin
DSPM (crown-jewel data) DSPM-01/02/03 Tag-based data classification (no Macie needed) for RDS/Aurora/DocDB/Neptune/Redshift/DynamoDB/EFS/OpenSearch/Kinesis/MemoryDB/FSx/Timestream — crown-jewel + public reachability + CAN_READ_DATA reader edges; public resource-policy exposure
AWS-resident secrets SECRET-01/02/05 Plaintext SSM String params that look secret, SecureString managed-key-vs-CMK, and a crown Secret node + the internet→role→secret reader path (metadata only — never a GetSecretValue)
AI-SPM AISPM-01/02/03, AIPATH-01 Blast radius of an AI resource's execution role — privilege-escalation-capable (AC-6), reaches crown data (AC-3), no network isolation (SC-7), and and the conditional AIPATH-01 (an AI resource that pairs unrestricted egress with a role that can escalate or read crown data — reported as capability, not as an observed route: no inbound reachability is claimed)

Compliance Framework Mapping

Every finding is tagged with applicable controls from:

Framework Coverage
CIS AWS Foundations Benchmark v3.0 IAM, S3, VPC, Logging, KMS, EC2, RDS, CloudFront, Lambda, EKS
PCI DSS v4.0 Requirements 1, 2, 3, 4, 6, 7, 8, 10, 11, 12
HIPAA 164.308, 164.312 (access control, audit, transmission, encryption)
SOC 2 Type II CC6 (logical access), CC7 (monitoring), A1 (availability)
NIST 800-53 Rev 5 AC, AU, CM, CP, IA, SC, SI families

Risk Scoring

The scanner computes a posture score from 0-100:

Score = 100 − (CRITICAL × 15  +  HIGH × 5  +  MEDIUM × 2  +  LOW × 0.5)
Grade Score Range
A 90 -- 100
B 80 -- 89
C 70 -- 79
D 60 -- 69
F 0 -- 59

CI/CD & AWS Security Hub Integration

The live scanner emits SARIF 2.1.0 (GitHub code scanning) and ASFF (AWS Security Hub), and gates pipelines via --fail-on:

# .github/workflows/aws-audit.yml
jobs:
  aws-security-audit:
    runs-on: ubuntu-latest
    permissions:
      security-events: write          # required to upload SARIF
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<ACCOUNT>:role/security-audit
          aws-region: eu-west-1
      - run: pip install boto3
      - run: python -m engine.aws_live_scanner --sarif results.sarif --fail-on HIGH
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: results.sarif
  • SARIF maps severity → level (CRITICAL/HIGH → error) and sets security-severity so findings surface in the GitHub Security tab.
  • ASFF imports into Security Hub: python -m engine.aws_live_scanner --asff f.json && aws securityhub batch-import-findings --findings file://f.json (≤100/call).

Shift-left IaC gate (no cloud, no hub). The offline IaC scanner (aws_offline_scanner.py) is the charter-clean PR gate — pure local static analysis, no AWS credentials, no network. It emits SARIF 2.1.0 with a real file:line (annotations land on the offending Terraform/CFN line, not a file-level note) and gates via --fail-on <severity> and --policy <file> (the pure-Python policy engine over the findings). The overwatch-iac-gate composite Action wraps it — egress-free (the opposite of the overwatch-image-scan ingest action: it POSTs nothing):

- uses: ./.overwatch/.github/actions/overwatch-iac-gate
  with: { path: infra/, fail-on: HIGH, policy: infra/policies.json }

Exit codes: 0 clean · 1 gate breach (a finding ≥ threshold or a fired policy) · 2 usage/environment error. An ide/ VS Code reference stub runs the same offline scanner on the open file and shows the findings inline as diagnostics (shell-only, network-clean).

  • Drift tracking: --baseline prev.json prints only NEW and RESOLVED findings.

OverWatch — Hosted CNAPP Platform (multi-account, agentless)

OverWatch is the product name for this CNAPP: the attack-path-graph layer that collapses a whole AWS estate into the ranked handful of internet → exposed workload → exploitable CVE → over-privileged role → crown-jewel-data paths, and names the choke point that severs the most.

Beyond the CLI, OverWatch ships a self-hosted platform backend for onboarding and continuously scanning many AWS accounts — the Wiz/Orca model, agentless and with no access keys.

Architecture: hub-and-spoke

One hub (an EC2 instance running the FastAPI service + worker + Postgres/SQLite state, in a dedicated security account) reaches every onboarded spoke account by assuming a read-only cross-account role (CnappScannerRole) that trusts only the hub role under a per-account sts:ExternalId (confused-deputy guard). Region/AZ are irrelevant to the API scan — control-plane endpoints are reachable from anywhere; the hub's own AZ never constrains coverage.

Onboarding

Deploy the single-account CloudFormation stack (deploy/cnapp-scanner-role.yaml) via a one-click Launch-Stack URL, apply the equivalent Terraform module (deploy/terraform/scanner-role/ — a structural test keeps it byte-equivalent to the CFN), or connect an entire AWS Organization with a service-managed StackSet (deploy/cnapp-stackset.md) that auto-enrolls current and future member accounts. The role attaches only SecurityAudit + ViewOnlyAccess (read-only of configuration/IAM — never workload data).

Air-gapped, zero-telemetry packaging

OverWatch makes zero telemetry / phone-home / update-check calls — every egress is an AWS API or an operator-configured opt-in seam (see NETWORK.md, enforced by tests/test_zero_telemetry.py). It packages for fully offline deployment: a multi-stage Dockerfile that installs from a pre-downloaded wheelhouse (--no-index), scripts/build_offline_bundle.sh (one transfer tarball), and docs/AIRGAP_RUNBOOK.md (AWS via VPC endpoints, vuln bundle from disk). Listed on AWS Marketplace as a self-hosted container product (deploy/marketplace/), priced on accounts under management (metered) with a contract SKU for air-gapped buyers.

Backend modules

Pure, dependency-injected, offline-testable:

Module Role
cnapp_onboarding.py Mint ExternalId (stored as a secret reference), build the CFN launch URL/CLI
cnapp_validate.py validate_connection: assume → account-match hard stop → read canary → org list; health + backoff
cnapp_registry.py AccountRegistry (accounts / scan_jobs / connection_health) over the dual-dialect state store
cnapp_service.py PlatformService facade + serialize_scanner + org rollup
cnapp_worker.py Async scan-job execution (traps engine exit, pre-validates creds, TOCTOU re-check)
cnapp_api.py FastAPI routes + workspace-scoped RBAC (fail-closed; Principal from an injected IdP-claims hook or the legacy-role shim; account_gate tenant isolation → 404), guarded import
cnapp_workspace.py Multi-tenancy (MSSP)WorkspaceStore: workspaces / members / platform-admins CRUD + the account↔workspace binding read helpers the isolation gate uses
cnapp_metering.py Usage metering — fail-open MeteringStore (append-only, exactly-once ledger); billable = accounts under management (account.active monthly gauge) + idempotent reconcile
cnapp_connectors.py Connector framework — route findings to Jira / Slack / PagerDuty / Splunk / webhook (pure renderers + injected http_post seam + rules engine + idempotent delivery ledger)
compliance_crosswalk.py Compliance breadth — sourced NIST 800-53 → 30+ framework crosswalk loader (accuracy-gated, fail-open); aws_live_scanner.crosswalk_scorecard derives per-framework coverage
aws_ingest.py External-vuln ingest — pure SARIF/CycloneDX/SPDX parsers (per-tool adapters) → own each CVE onto a graph node → enrich from OverWatch's own OSV/EPSS/KEV bundle → re-run reachability so CVEs rank by attack-path exploitability, not CVSS
aws_copilot.py Grounded-RAG copilot — self-contained BM25 retrieval over the account's own scan (findings / paths / choke points); extractive-by-default (abstains rather than hallucinating), optional injected LLM seam
aws_aispm.py AI-SPM pillar — pure classifiers for the blast radius of an AI resource's execution role (privesc-capable / reaches-crown-data / no-network-isolation) fused onto the graph post-clobber; emits AISPM-01..03 + the conditional AIPATH-01
aws_cdr.py CDR-lite — normalize GuardDuty / Security Hub ASFF / CloudTrail-anomaly detections, fold onto the stored graph as THREAT_ON, re-run reachability so a detection on an internet→crown/admin path escalates to a ranked incident (reuses aws_ingest predicates; no aws_correlate change)
aws_edr.py Runtime-sensor (EDR/CWPP) ingest — the in-charter substitute for a runtime sensor: normalize the customer's EXISTING CrowdStrike / Falco / GuardDuty-Runtime / OCSF output → detections fold through the aws_cdr THREAT_ON/incident path; a sensor-inventory feed drives runtime_monitored coverage. Pure; reuses aws_cdr/aws_correlate read-only (no sensor, no aws_correlate edit)
aws_malware.py Malware-finding ingest — normalize the customer's EXISTING malware scan output (Amazon GuardDuty Malware Protection, ClamAV, YARA) → fold as THREAT_ON → reachability-ranked malware incident (MAL-01/02/03, incl. the malware∩DSPM cross-engine hit in a crown store). EICAR/test-signature suppression. Pure; reuses aws_cdr. Native scanning over file bytes stays deferred (EBS filesystem parse)
aws_dspm.py DSPM read-surface — normalize the already-collected sensitivity signal (Macie score / classification tags) into data_types (PII/PCI/PHI/…) + a tier at read time, and compute the crown-jewel data inventory + exposure + a classification-coverage gap over the STORED graph. Pure; no new scan, no file-content read, aws_correlate untouched
aws_forensics.py Cloud-forensics timeline — reconstruct who-did-what-when around a resource from read-only CloudTrail management events, correlated with graph / findings / detections; fail-open FORENSIC-00 behind an injected seam
aws_wql.py WQL graph query — a typed, bounded JSON query language compiled to the frozen aws_graph traversal primitives (no free Gremlin / Neptune / eval). parse() is the security boundary (rejects any unknown field/op/predicate/prop → WQLError); evaluate() returns deterministic (kind,id)-sorted node rows. Mirrored byte-for-byte in frontend/src/lib/wql.ts for SAMPLE==LIVE, guarded by a cross-language parity fixture
aws_controls.py Saved-query-as-Control — pure transform of a matched WQL result into a display-only WARN finding (status=WARN → never touches the FAIL-only posture score); mirrored in frontend/src/lib/controls.ts
aws_policy.py Policy-as-code engine (the in-charter Rego/OPA substitute — a Go binary would break zero-telemetry/air-gap) — a closed, typed pure-Python DSL extending Controls with a finding-catalog predicate (compliance-as-code: match by check_id/section/severity/compliance) + a graph clause (WQL) combined any/all; a firing rule overlays a display-only WARN POLICY-xx finding. Config-driven (CNAPP_POLICIES); mirrored in frontend/src/lib/policy.ts
aws_registry_oci.py Non-AWS OCI registry pull adapter — pure Docker Registry v2 client (parse-ref / WWW-Authenticate Bearer dance / manifest + manifest-list / tag enumeration / layer-blob pull) driven entirely by injected aws_layer_fetch seams; fail-closed on a partial rootfs (zstd/undecodable/truncated/over-budget); covers GHCR / Docker Hub / Harbor / ACR
aws_registry_connectors.py Registry-connector config layer — parse/validate CNAPP_REGISTRIES, resolve a secretsmanager:///ssm:// secret-ref to credentials transiently, host-qualify + host-consistency-guard refs, orchestrate enumerate→pull→side-scan, and mask the secret out of API views. Pure

HTTP API surface

All routes delegate to PlatformService; admin routes stay on the private hub control plane. POST /accounts (onboard → launch URL), POST /accounts/{id}/validate, GET /accounts, POST /scans, GET /scans/{job_id}, GET /accounts/{id}/summary|issues|findings|paths|graph, GET /org/overview, GET /org/findings; graph query (WQL)POST /accounts/{id}/graph/query (typed query → matching nodes; malformed → 400), GET /accounts/{id}/graph/blast-radius, GET /projects[/{id}], GET /controls, GET /policies; copilotPOST /accounts/{id}/copilot, POST /org/copilot; ingestPOST /accounts/{id}/ingest, GET /accounts/{id}/vulns[/{cve}], GET /org/vulns; CDRPOST /accounts/{id}/detections, GET /accounts/{id}/{detections,incidents}, POST /accounts/{id}/detections/refresh, GET /org/incidents; runtime (EDR)POST /accounts/{id}/edr/ingest (ingest tier; sensors + detections), GET /accounts/{id}/edr/coverage, GET /org/edr/coverage; malwarePOST /accounts/{id}/malware/ingest (ingest tier; GuardDuty-Malware / ClamAV / YARA); data (DSPM)GET /accounts/{id}/data/inventory, GET /org/data/inventory; forensicsGET /accounts/{id}/forensics/timeline; supply-chainGET /accounts/{id}/sbom/{subjects,snapshots,diff}, GET /accounts/{id}/components, GET /accounts/{id}/license-findings, GET /accounts/{id}/vex; non-AWS registriesGET /registries (secret-masked), GET /registries/images (cached rows), POST /registries/{id}/scan (admin — a live agentless pull + side-scan); connectorsPOST/GET /connectors, PUT/DELETE /connectors/{id}, POST /connectors/{id}/{enable,rotate-secret,test}, .../rules CRUD, POST /connectors/rules/preview (dry-run), POST /accounts/{id}/notify, GET /connectors/{id}/deliveries, GET /notifications; multi-tenancyPOST/GET /workspaces, GET/PUT/DELETE /workspaces/{ws}, GET/POST /workspaces/{ws}/members, DELETE /workspaces/{ws}/members/{principal}, GET/POST/DELETE /admin/platform-admins, GET /workspaces/{ws}/usage, GET /admin/usage, POST /admin/usage/reconcile.

Multi-tenancy (MSSP)

One hub serves many tenant workspaces, each isolated (a principal in workspace A can never read/write/scan/meter workspace B — a cross-tenant object returns 404) and metered (billable = accounts under management). RBAC is workspace-scoped: an injected current_principal hook maps an authenticated caller (IdP/JWT claims) to {workspace_id: role} memberships + an optional platform-superadmin; the target workspace travels in an X-Workspace-Id header. A default workspace holds every account of a single-tenant deployment, so the console and the legacy single-role auth hook keep working unchanged.

Continuous scanning and drift digests (CTEM)

Set a per-account scan cadence (hourly / daily / weekly / custom) and OverWatch scans on a schedule — a scheduler_tick enqueues due accounts (a failing scan backs off exponentially, never flapping) and drains the queue. Each scan folds into the finding-lifecycle store (drift / trend / MTTR), and one drift digest"what changed since last scan" (new / resolved / reopened, posture Δ, newly-exposed on an attack path, SLA breaches, compliance drift) — is delivered per scan through the connectors, to the connectors that opt in (config.digest). Digests are idempotent per window (a separate digest_log ledger; a failed one is retried) and gated to material change so a quiet scan sends nothing. The console shows a "What changed" + posture-trend card, a per-account cadence selector, and a digest toggle per connector. Routes: POST /scans/schedule-tick, PUT /accounts/{id}/schedule, GET /accounts/{id}/{trend,mttr,drift}, GET /digests, POST /accounts/{id}/digest/preview.

Compliance breadth: 30+ frameworks

Every check is tagged with a NIST 800-53 Rev 5 control, so instead of hand-tagging every check against every regime, OverWatch cross-walks its 38 in-use NIST controls to 34 more frameworks (ISO 27001:2022, FedRAMP, NIST 800-171 / CMMC, NIST CSF 2.0, PCI-DSS 4.0, SWIFT, DORA, HIPAA, HITRUST, GDPR/CCPA, CSA CCM v4, CIS Controls v8, COBIT, NIS2, ATT&CK-Cloud, …) and derives coverage transitively — each derived control reads "satisfied via NIST 800-53 AC-3 (crosswalk)" with a per-mapping confidence tier and authoritative source citations. The crosswalk was sourced from published authoritative mappings and web-verified by an adversarial review (which caught + fixed real mapping errors); a CI accuracy validator gates the shipped data (no fabricated ids, no native-framework targets). The 5 directly-tested frameworks keep their per-check tags and are byte-identical; derived data is informational (confirm scope with your assessor). Routes (viewer): GET /compliance/frameworks, GET /compliance/crosswalk, GET /accounts/{id}/compliance?min_confidence=, GET /org/compliance. Surfaced in the console Compliance screen (directly-tested vs crosswalk-derived, family + confidence filters, per-control provenance, source links).

Connectors

Notify your own tools (agentless, read-only on targets). OverWatch can route findings to the operator's own Jira Cloud, Slack, PagerDuty (Events v2), Splunk HEC, or a signed generic webhook, under a rules engine (severity floor / section / check glob / account glob / on-attack-path / framework). It makes no AWS call against a scanned account — the only outbound is HTTP to the operator's endpoints. Every credential is stored only as a secretsmanager:///ssm:// reference (never plaintext, never returned over the API); connectors are admin-only, disabled by default, and delivery is idempotent (a re-scan never re-sends an unchanged finding, and a failed send is retried). Configured from the console Settings → Integrations screen (add/test/enable, per-connector routing rules with a dry-run preview, and a deliveries audit log).

External vulnerability ingest

Rank CVEs by reachability, not CVSS. Upload the output of any SCA scanner — SARIF (Trivy / Grype / Snyk), CycloneDX (with a vulnerabilities[] array or VEX analysis.state), or SPDX / Syft SBOMs — and OverWatch owns each CVE against the AWS resource it belongs to, dedups it across sources (one row per (account, node_id, cve); N reporters = a sources set-union), and enriches it from the same OSV / EPSS / KEV bundle a native side-scan uses (so KEV/EPSS/exploit are byte-identical, never inferred from the doc). The owned CVE is attached as a HAS_VULN edge and the attack-path engine is re-run — a membership check on the stored paths would structurally miss the path a newly-reported KEV reveals — so an ingested KEV on an internet-exposed, path-to-crown host earns the identical CRITICAL as a native one, while a high-CVSS but unreachable CVE ranks as noise. VEX not_affected / false_positive is suppress-but-track (owner VEX outranks a scanner's "exploitable"; the row is retained and counted, never a silent hole); CodeQL SARIF (SAST, no CVE) is excluded. Reachable survivors flow into the existing connectors as two check-level aggregates (VULN-ING-KEV / VULN-ING) and a newly-reachable KEV rides the drift-digest newly_on_path signal. Read-only on scanned accounts — it works purely off the uploaded doc + the stored graph. Routes: POST /accounts/{id}/ingest (admin), GET /accounts/{id}/vulns (ranked, faceted by KEV / on-path / source / band), GET /accounts/{id}/vulns/{cve}, GET /accounts/{id}/ingest/docs, POST /accounts/{id}/vulns/refresh (admin), GET /org/vulns. Surfaced in the console Vulnerabilities screen — a reachability-anchored inventory (the reachability chip, not CVSS, is the visual weight) with a SARIF/CycloneDX/SPDX upload affordance and an Overview roll-up (N external CVEs · M reachable · K reachable-KEV).

Supply-chain ingest: SBOM diff, license, VEX

The same ingest seam also owns the software bill of materials itself, not just the CVEs inside it. Each uploaded CycloneDX / SPDX / Syft SBOM is persisted as an account-scoped snapshot (aws_ingest.py walks the doc — including bom-ref-less and nested components — and mints a canonical purl identity per component), so OverWatch can answer three supply-chain questions no single scan can:

  • SBOM diff over time (aws_sbom_diff.py) — compare two snapshots of the same subject (image / repo) and get added / removed / changed components, where "changed" surfaces both version and license transitions. Snapshots are keyed f"{account}:{doc_id}" so two accounts pushing the same image never collide, and a tie on ingest epoch breaks deterministically (newest snapshot_id wins).
  • License policy (aws_license.py) — every component's declared license is normalized to an SPDX id, bucketed into a category (permissive / weak-copyleft / strong-copyleft / network-copyleft / proprietary / unknown), and run against a policy (deny / warn / allow) whose compound AND-expressions are parenthesized so a mixed (MIT AND GPL-3.0) clause can't be silently mis-judged. Surfaced as license findings per account.
  • VEX (aws_vex.py) — ingest OpenVEX and CSAF-VEX documents; a vendor not_affected / fixed statement is applied product-wide only (never purl-blindly, so one component's VEX can't over-suppress an unrelated CVE), recorded to a vex_statements ledger, and folded into the same suppress-but-track path as inline CycloneDX VEX.

State lives in four new tables (sbom_snapshots, sbom_components, sbom_snapshot_cves, vex_statements; SCHEMA_VERSION 8, dual sqlite/Postgres). Because CI/CD pipelines push SBOMs with a machine token, ingest gets its own RBAC tier below admin (viewer < ingest < admin): a pipeline can POST /accounts/{id}/ingest but can't read another tenant's graph or touch connectors. A shell-only GitHub Action (.github/actions/overwatch-image-scan/) builds an image SBOM and pushes it in one CI step. Read routes (viewer): GET /accounts/{id}/sbom/{subjects,snapshots,diff}, GET /accounts/{id}/components, GET /accounts/{id}/license-findings, GET /accounts/{id}/vex. Surfaced in the console Supply Chain screen (snapshot picker + diff, component inventory with license chips, license-policy findings, VEX ledger).

Agentless ECR registry enumeration + opt-in layer-pull. Beyond CI-pushed SBOMs, OverWatch scans the registry itself. Tier A (always on, no new grant) widens the native ECR scan-finding sweep from the newest image to the newest-N tagged images per repo (bounded per repo + a per-scan aggregate budget). Tier B (opt-in two-key: the --side-scan-images flag and the CnappImageLayerPull grant — repo-scoped, tag-gated on cnapp:imagescan) pulls image layers, reconstructs the rootfs, and runs OverWatch's own SBOM→OSV pipeline (Inspector-independent) — fail-closed on a partial/corrupt rootfs, and the layer bytes are fetched through one hardened, allowlisted aws_layer_fetch.http_get (HTTPS + *.amazonaws.com only, re-validated on every redirect). Native (ecr-native-scan) and own-SBOM (ecr-sidescan) CVEs converge on the same ECRImage node (split by a scan_source chip) and persist as diffable snapshots; a registry-only image (not deployed) carries HAS_VULN but never enters an attack path — it ranks shift-left, never a false CRITICAL (aws_correlate.py stays frozen). Read routes GET /accounts/{id}/registry/{repos,images}; a Registry tab on the Supply Chain screen (deployed vs registry-only, scan-source chips, a "not reachable" label).

Non-AWS registry connectors (GHCR / Docker Hub / Harbor / ACR). The same agentless pull + own-SBOM→OSV pipeline extends to a customer's non-AWS OCI registries. An operator declares a connector in CNAPP_REGISTRIES ([{connector_id,type,host?,auth?,username?,secret_ref?,images?, repositories?}], JSON or file path; disabled-by-default; a credential is a secretsmanager:// / ssm:// reference, never plaintext) and aws_registry_oci.py speaks the standard Docker Registry v2 Bearer-realm token dance — one adapter covers GHCR, Docker Hub, Harbor, and ACR (including ACR's service-principal, which authenticates as plain Basic), so there is no per-registry auth code; GCR / Artifact Registry is deferred (its OAuth needs RS256 signing, absent from the pure-Python air-gap wheelhouse). The pull reuses the SAME hardened egress file (aws_layer_fetch.registry_request / registry_blob_get) — so the zero-telemetry allowlist does not grow — under a per-call host allowlist (the config host + the auth realm learned at runtime; never "any https"); a blob 3xx is followed only to a non-SSRF-target HTTPS host (IMDS / cloud-metadata / loopback / any private address refused, IP-literal encodings normalised), and the Authorization header is stripped on any cross-host hop. Pulled images run through the registry-agnostic scan_pulled_layers (shared with ECR, so the fail-closed-on-partial-rootfs contract can't diverge — a zstd/undecodable/truncated layer set fails closed, never a false clean), and a host-consistency guard refuses any ref that resolves off the connector's host so a credential can never travel to a third-party registry. Results are display-only (never posture or the frozen attack-path graph). Routes GET /registries, GET /registries/images, POST /registries/{id}/scan (admin); a Registries console screen (aws_registry_connectors.py config layer; frontend/src/routes/Registries.tsx).

Shared Postgres state

Opening the state store with a postgresql:// URL runs the whole state plane (finding lifecycle/drift/waivers + the account registry) on a real Postgres via psycopg3 — the shared store for the hub. Both stores route through one Backend abstraction (cnapp_backend.py); the sqlite path is byte-identical and a missing driver / unreachable server fails loudly rather than falling back to a local file.

Graph query (WQL) & Controls

WQL gives the security graph a queryable surface without a Gremlin/Neptune backend (both would break the self-hosted / air-gap charter). A query is a closed, typed JSON object — no free text, no regex, no eval — compiled by aws_wql.py to the frozen aws_graph traversal primitives. parse() is the security boundary: any unknown field, op, predicate, or non-whitelisted prop is rejected with WQLError (→ HTTP 400), and limit/max_hops are clamped, so a query can never escape the grammar or run unbounded.

// "crown-jewel S3 buckets reachable from the internet" — the toxic-combination query
{ "kind": "S3Bucket",
  "where": { "op": "and", "of": [
    { "pred": "crown_jewel" },
    { "pred": "reachable_from", "target": "internet" } ] } }

Predicates: kind · has_prop · prop (eq/neq/in/gt/gte/lt/lte/exists/contains) · prop_glob · has_edge (in/out) · reachable_from internet · reaches crown|admin · crown_jewel, composed with and/or/not. evaluate() returns deterministic, (kind,id) code-point-sorted node rows. POST /accounts/{id}/graph/query runs it live; the whole compiler is mirrored byte-for-byte in frontend/src/lib/wql.ts so SAMPLE mode (querying the graph fixture client-side) returns identical rows — a cross-language parity fixture (scripts/gen_wql_parity.pywql_parity.json) fails CI on any drift.

Controls turn a saved WQL query into governance: configure CNAPP_CONTROLS ([{id,name,query,severity?,description?}], a JSON list or file path; fail-safe) and each matching query overlays a display-only WARN finding into the catalog. Controls are read at request time and are strictly display-only — they never re-run scoring (the FAIL-only posture score stays baked at scan time) and never trigger notifications. GET /controls returns the org roll-up (match count per account, WARN/PASS), and the console's Query screen is a guided predicate-builder + raw-JSON editor + saved-Controls library over it.

Policy-as-code (aws_policy.py) is the custom-rule engine — the in-charter substitute for Rego/OPA (a Go binary would break the zero-telemetry / air-gap charter). A policy is a closed, typed JSON rule (config-driven via CNAPP_POLICIES) that combines a graph condition (a WQL query) and/or a finding-catalog condition — the new compliance-as-code surface: match by check_id glob / section / severity / status / compliance framework+control, e.g. "every check mapped to PCI-DSS 3.4 must PASS". The two clauses combine any/all; a firing rule overlays a display-only WARN POLICY-xx finding (never touching the posture score). GET /policies returns the org roll-up, and policy.ts mirrors the engine for SAMPLE==LIVE. The same engine gates PRs offline via aws_offline_scanner.py --policy (see CI/CD).

Runtime (EDR-ingest correlator)

OverWatch is agentless — it never deploys a runtime/eBPF sensor (that would break the zero-telemetry charter). Instead it ingests the customer's EXISTING runtime sensor and correlates it onto the attack-path graph — the in-charter substitute for runtime protection. Two push-ingest feeds via POST /accounts/{id}/edr/ingest (machine ingest tier):

  • Detections (CrowdStrike Falcon · Falco · GuardDuty Runtime Monitoring · generic OCSF 2004) normalize to the shared model and fold through the aws_cdr THREAT_ON path (out of E_PATH, so aws_correlate stays byte-frozen): a runtime alert on an internet→crown/admin workload escalates to a ranked incident (EDR-02, its own "Runtime" section, never mislabeled as a GuardDuty cloud threat).
  • Sensor inventory ("which workloads have a healthy sensor right now") drives a runtime_monitored node prop, overlaid at read time (a side table — graph_full stays the pristine scan seed). This powers GET /accounts/{id}/edr/coverage (monitored/total per kind
    • the UNMONITORED workloads ranked by attack-path exposure) and the Runtime console screen, and it finally makes the Batch-2 WQL query real: "workloads with no endpoint sensor" now returns the true gap set (EDR-01, a display-only WARN — coverage gaps never tank posture).

It substitutes for runtime detection and coverage visibility; it explicitly does not do real-time blocking, in-kernel telemetry, or DAST — it makes your EDR reachability-aware and tells you exactly which workloads it's blind to.

Data security (DSPM) & malware ingest

Both engines ship the buildable-now, agentless surface; native scanning over file bytes stays deferred behind the pre-committed EBS-filesystem-parse seam (aws_sidescan_fs.DissectExtractor).

  • DSPM (aws_dspm.py) — OverWatch already classifies crown-jewel data stores agentlessly during a scan (Amazon Macie automated scores + data-classification tags → crown_jewel + sensitivity, across 13 store kinds, with CAN_READ_DATA reader edges). This is the read-time surface over that signal: it normalizes the heterogeneous sensitivity into explicit data_types (PII/PCI/PHI/FINANCIAL/SECRET) + a sensitivity_tier, and computes the sensitive-data inventory + exposure + a classification-coverage gap (crown stores whose data type is unknown — the honest "enable Macie / tag it" signal, DSPM-GAP WARN) — all from the STORED graph, no new scan, aws_correlate untouched. The new props are WQL-queryable (GET /accounts/{id}/data/inventory + the Data Security console screen), so "publicly exposed stores holding PII" is a real query.
  • Malware (aws_malware.py) — ingest the customer's EXISTING malware scan output (GuardDuty Malware Protection, ClamAV, YARA; EICAR-suppressed) → fold as THREAT_ON → reachability-ranked malware incident: MAL-01 (on a critical attack path), MAL-02 (on a reachable workload), MAL-03 (a malicious object IN a Macie-classified crown store — the malware∩DSPM win). It reuses the exact aws_cdr incident spine; aws_correlate stays byte-frozen. OverWatch running its own YARA/ClamAV over file bytes is the piece that waits on the filesystem parse.

Web console

A React 19 + Vite + TypeScript + Tailwind SPA over the hub API — the operator surface, styled to match the exported HTML report. Twelve screens over one design system, led by Overview (posture dashboard, org ↔ account scope), Attack Paths (ranked toxic-combination worklist + an interactive graph explorer showing each path's gated-multiplicative score breakdown), Findings (unified deduped queue with source sub-tabs + a risk → business-impact → step-by-step remediation detail panel), and Cloud Accounts with a keyless 5-step onboarding wizard (server-minted ExternalId + CloudFormation / Org StackSet) — plus Vulnerabilities, Runtime (EDR sensor coverage + ranked blind-spots + runtime & malware incidents), Data Security (DSPM sensitive-data inventory — data types, exposure, readers + classification gaps), Supply Chain, Inventory, Query (WQL console — guided predicate builder + raw JSON + saved Controls), Projects, Identity, Compliance, Remediation, Reports, and Settings → Integrations. Every view is a shareable deep link — scope and each open panel live in the URL, so a pasted /attack-paths?scope=…&path=… reopens exactly what you were looking at — and an interactive product tour replays five canned scenarios over the live console (navigating, setting scope, spotlighting real elements, opening real panels) for a zero-AWS guided walkthrough. It runs on engine-shaped sample fixtures with zero AWS, and a VITE_DATA_SOURCE=live build flips every screen to the live hub. cnapp_api.create_hosted_app(service, static_dir="frontend/dist") serves the API under /api and the SPA at / (with a history-API fallback) as one deployable. See frontend/README.md.

cd frontend && npm install && npm run dev     # http://localhost:5173  (sample data, no AWS)

Status: backend + onboarding + validation + registry + scan orchestration + live Postgres state + the web console (Overview / Attack Paths / Findings / Vulnerabilities / Supply Chain / Cloud Accounts + onboarding wizard, plus Inventory / Identity / Compliance / Remediation / Reports / Settings — with shareable deep links and an interactive product tour) + the connector framework (Jira / Slack / PagerDuty / Splunk / webhook + Settings screen) + compliance breadth (30+ frameworks via the NIST-800-53 crosswalk) + continuous scheduled scanning + drift digests (CTEM cadence + lifecycle drift/trend/MTTR + per-scan digests through the connectors) + reachability-verified vulnerability ingest (SARIF / CycloneDX / SPDX) + supply-chain ingest (SBOM diff + SPDX license policy + OpenVEX/CSAF-VEX, with an ingest-only RBAC tier and a GitHub Action) + agentless ECR registry enumeration + opt-in layer-pull (Tier-A native scan findings across all tagged images; Tier-B own-SBOM CVEs behind a two-key grant, converging on one node and persisting as diffable snapshots) shipped. Remaining: a Postgres connection pool.


When to Use Which Scanner

Scenario Recommended Scanner
Pre-deployment IaC review (CloudFormation / Terraform) IaC Scanner (aws_offline_scanner.py)
Live AWS account security audit Live Scanner (aws_live_scanner.py)
CI/CD pipeline gate for infrastructure code IaC Scanner
Compliance assessment against CIS AWS Benchmark Live Scanner
No AWS credentials available, only code to review IaC Scanner
Comprehensive audit of a production account Both -- IaC Scanner on templates, Live Scanner on live account

Project Structure

AWS-Security-Scanner/
├── engine/            # the scanning engine — collection, checks, correlation, scoring
│   ├── aws_offline_scanner.py      # IaC Security Scanner (CloudFormation + Terraform, no credentials)
│   ├── aws_live_scanner.py         # Live Audit Scanner v3.0.0 (44 sections, graph, exposure+L7, deep-plane, correlate, effperm, state, ciem, sidescan, KSPM/KIEM, flow-logs, backends, remediate, codetocloud, finding-detail, engine-EOL, winvuln, DSPM, secrets, least-priv, AI-SPM, CDR, forensics, copilot, vuln-ingest, supply-chain, registry)
│   ├── aws_remediate.py            # Remediation engine — prioritized plan + remediation-as-code + exports, pure (read-only)
│   ├── aws_codetocloud.py          # Code-to-cloud — IaC index + tiered T1–T5 matcher (TF/CFN → finding source), pure
│   ├── aws_graph_neptune_loader.py # Neptune live loader — bulk-load/openCypher runners (mock-tested), pure builders
│   ├── aws_exposure.py             # Internet-reachability oracle — 4-gate AND, pure/testable (stdlib)
│   ├── aws_deepplane.py            # Deep-plane parsers (Inspector/Macie/GuardDuty/AA) + CAN_READ_DATA (stdlib)
│   ├── aws_correlate.py            # Attack-path correlation engine — enumerate/score/rank/choke (stdlib)
│   ├── aws_graph.py                # SecurityGraph — nodes/edges, bounded traversal, graph.json (stdlib)
│   ├── aws_effperm.py              # Effective-permissions solver — identity∩boundary∩SCP, deny-wins (stdlib)
│   ├── aws_unused.py               # CIEM unused-access/right-sizing — Access-Analyzer+SLAD, dormancy (stdlib)
│   ├── aws_sidescan.py             # Agentless CWPP core — inventory + dpkg/rpm/apk vercmp + OSV match + secrets → HAS_VULN (stdlib)
│   ├── aws_sidescan_ebs.py         # EBS Direct-API block plane — plan/delta/checksum/sparse/cleanup (stdlib; live I/O deferred)
│   ├── aws_graph_neptune.py        # Neptune export — Gremlin bulk-CSV + openCypher MERGE + round-trip (stdlib)
│   ├── aws_kube.py                 # Agentless KSPM/KIEM — CIS-EKS + K8s RBAC via read-only K8s API, cross-plane pod→AWS-role (stdlib)
│   ├── aws_flowlog.py              # VPC Flow-Log overlay — observed-traffic SG scope-down/unused-port/reject-talker, opt-in (stdlib)
│   ├── aws_secrets.py              # AWS-resident secrets — SSM/Secrets-Manager posture classifiers (metadata only, pure)
│   ├── aws_leastpriv.py            # Least-privilege policy generation — GRANTED-minus-USED right-sizing from SLAD, pure
│   ├── aws_ingest.py               # External-vuln ingest — SARIF/CycloneDX/SPDX parsers → own onto graph → reachability re-rank, pure
│   ├── aws_copilot.py              # Grounded-RAG copilot — self-contained BM25 over the scan's own corpus, extractive/abstains, pure
│   ├── aws_aispm.py                # AI-SPM — AI execution-role blast-radius classifiers (privesc/reaches-crown/no-net-iso), pure
│   ├── aws_cdr.py                  # CDR-lite — normalize GuardDuty/ASFF/CloudTrail detections → THREAT_ON → reachability-ranked incidents, pure
│   ├── aws_edr.py                  # Runtime-sensor (EDR/CWPP) ingest — CrowdStrike/Falco/GuardDuty-Runtime/OCSF → detections + runtime_monitored coverage, pure
│   ├── aws_malware.py              # Malware-finding ingest — GuardDuty-Malware/ClamAV/YARA → THREAT_ON → MAL-01/02/03 incidents (malware∩DSPM), pure
│   ├── aws_dspm.py                 # DSPM read-surface — normalize sensitivity → data_types/tier + crown-jewel data inventory + classification-gap, pure
│   ├── aws_forensics.py            # Cloud-forensics timeline — read-only CloudTrail events correlated with graph/findings/detections, pure
│   ├── aws_wql.py                  # WQL — typed, bounded JSON query language compiled to the frozen aws_graph primitives (parse = security boundary), pure
│   ├── aws_controls.py             # Saved-query-as-Control — matched WQL result → display-only WARN finding, pure
│   └── aws_policy.py               # Policy-as-code — pure typed DSL (compliance-as-code + graph clause) → POLICY-xx WARN, pure
├── store/             # persistence — imported by both, imports neither
│   ├── aws_state.py         # Persistent state store — lifecycle/drift/MTTR/waivers (pure sqlite3)
│   └── aws_state_dialect.py # Postgres/SQLite dialect — DDL/upsert/parse_state_url/row-shim (stdlib)
├── ide/                     # VS Code reference stub (shift-left IaC diagnostics via the offline scanner) — spec + stub, unpublished
│   ├── cnapp_onboarding.py · cnapp_validate.py · cnapp_registry.py · cnapp_service.py · cnapp_worker.py · cnapp_api.py · cnapp_connectors.py · cnapp_secrets.py  # Hosted platform backend
│   └── (cnapp_backend.py lives in store/ with aws_state — the persistence trio is mutually recursive)
├── frontend/                # OverWatch web console — React 19 + Vite + TS + Tailwind SPA (Overview / Attack Paths / Findings / Cloud Accounts + onboarding wizard)
├── deploy/                  # CloudFormation scanner-role + Org StackSet + hub-role templates
├── tests/
│   ├── test_live_scanner.py # 69 unit tests (mock boto3, no credentials needed)
│   ├── test_cnapp_phase1.py # 32 unit tests (graph, chains, trust, org fan-out, compliance)
│   ├── test_exposure.py     # 35 unit tests (internet-exposure FP/FN catalog + attack path)
│   ├── test_deepplane.py    # 44 unit tests (deep-plane FP/FN catalog + collectors + flagship)
│   ├── test_correlate.py    # 22 unit tests (path enumeration + scoring + choke points)
│   ├── test_effperm.py      # 32 unit tests (eval-order truth table, SCP/boundary scenarios)
│   ├── test_state.py        # 22 unit tests (lifecycle, coverage-gated resolve, waivers, MTTR)
│   ├── test_unused.py       # 21 unit tests (dormancy, right-sizing, down-rank, collection)
│   ├── test_phase5_integration.py # 17 tests (ceiling edge-pruning + defect regressions)
│   ├── test_sidescan.py     # 63 unit tests (dpkg/rpm/apk vercmp, parsers, OSV match, secrets, edges)
│   ├── test_sidescan_ebs.py # 21 unit tests (plan/delta-zeroing/checksum/SparseImage/cleanup)
│   ├── test_graph_neptune.py     # 14 unit tests (Gremlin CSV, openCypher, round-trip)
│   ├── test_state_dialect.py     # 22 unit tests (URL parse, qmark→pyformat, upsert, DDL parity)
│   ├── test_phase6_integration.py # 15 tests (ATTACK-02-from-agentless pillar + defect regressions)
│   └── samples/             # Sample IaC files and reports
├── scripts/
│   └── validate_live.py     # Read-only live-account validation harness
├── docs/
│   ├── overwatch-logo.png   # brand logo (README banner) + overwatch-mark* (console + report)
│   └── banner.svg
├── CLAUDE.md                # Developer documentation
├── CHANGELOG.md             # Release notes
├── SECURITY.md              # Security policy / responsible disclosure
├── .gitignore
├── LICENSE                  # GPL-3.0
└── README.md

Requirements

Scanner Requirements
IaC Scanner (aws_offline_scanner.py) Python 3.10+, optional pyyaml for CF YAML templates
Live Scanner (aws_live_scanner.py) Python 3.10+, boto3, AWS credentials with SecurityAudit IAM policy

Disclaimer

These tools are for authorised security assessments only. The live scanner performs read-only API calls and never modifies AWS resources. The IaC scanner performs pure static analysis with no AWS connectivity. Always ensure you have explicit authorisation before scanning.


License

GPL-3.0 License -- see LICENSE.

About

Overwatch is an open source CNAPP solution for AWS workloads and AI Security Posture Management (SPM)

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages