# Module S09 — Cloud Posture Harness Engineering

**Course**: 2A — Building AI Harnesses for Cybersecurity
**Module**: S09 — Cloud Posture Harness Engineering
**Duration**: 90 minutes
**Level**: Senior Engineer and above
**Prerequisites**: S00–S08 complete

> *A harness that knows what is deployed before it assesses it. Cloud asset discovery across AWS, Azure, and GCP; graph-based attack-path reasoning; event-driven posture monitoring that catches drift before it becomes a breach; and remediation workflows with an approval gate that is never skipped in production.*

---

## Learning Objectives

1. Build a cloud asset discovery harness that enumerates resources across AWS Security Hub, Azure Resource Graph, and GCP Security Command Center, with AI-workload tagging (model endpoints, agents, vector stores).
2. Model the cloud attack surface as a graph and implement path-finding from externally reachable assets to sensitive data stores through IAM escalation and public-exposure chains.
3. Design an event-driven posture monitor that consumes CloudTrail and Activity Log events, triggers targeted re-assessments on security-relevant changes, and maps CIS benchmark controls to harness-checked policies.
4. Produce multi-audience posture reports (engineer finding list, CISO risk summary, auditor compliance evidence) and implement automated remediation behind a mandatory approval gate.

---

# S09.1 — AI Workload Discovery and Asset Inventory

The cloud security harness's first job is not to assess risk. It is to know what is deployed. You cannot defend an asset you have not inventoried, and in a cloud account that changes hundreds of times a day, the inventory is never a static list. It is a continuously reconciled graph of resources, their relationships, and their exposure.

## Why asset discovery comes first

Cloud posture tools fail in a characteristic order. They fail at inventory first, then at assessment. A CSPM tool that does not know about a forgotten S3 bucket cannot flag it as public. A CWPP that does not know about an ephemeral container cannot assess its runtime. The discovery layer is the load-bearing foundation — every downstream capability (attack-path analysis, posture monitoring, remediation) depends on an accurate, current inventory.

The drift problem compounds this. Cloud infrastructure is not a static deployment — it is a continuously changing graph. A Terraform apply, a console click, an autoscaling event, a Lambda deployment, an IAM policy update: each changes the posture. A snapshot taken at midnight is stale by morning. The inventory must be continuously reconciled, not periodically snapshotted.

## Native discovery services

Each cloud provider exposes a service that enumerates resources and surfaces security-relevant findings. The harness wraps these as discovery tools, not replaces them.

| Provider | Service | Scope |
| --- | --- | --- |
| **AWS** | Security Hub + AWS Config | Aggregates findings from GuardDuty, Inspector, Config rules, and partner products; Config records resource configuration state and changes |
| **Azure** | Resource Graph + Defender for Cloud | Resource Graph queries the entire Azure estate with Kusto; Defender for Cloud aggregates posture findings and now includes AI security posture |
| **GCP** | Security Command Center (SCC) | Enumerates assets, surfaces findings from Event Threat Detection and Web Security Scanner; Premium tier adds posture management |

Microsoft Defender for Cloud now supports AI security posture natively. It discovers AI workloads, builds an AI software bill of materials (BOM), tracks externally reachable AI endpoints, and maps attack paths across Azure, AWS, and GCP from a single pane. For an AI-heavy estate this is the most efficient starting point — the discovery and the AI-workload tagging are built in rather than layered on.

## Harness-side discovery: provider APIs

The native services are necessary but not sufficient. A harness adds three things they do not provide: cross-provider normalization, AI-specific asset tagging, and structured output suitable for graph reasoning. The discovery layer wraps the provider APIs and normalizes everything into a single resource schema:

```typescript
interface CloudAsset {
  // Identity
  asset_id: string;          // provider-native unique ID
  arn: string;               // canonical ARN or equivalent
  provider: "aws" | "azure" | "gcp";
  region: string;
  account_id: string;
  service: string;           // e.g. "s3", "compute", "iam"

  // Classification
  resource_type: string;     // normalized: "storage", "compute", "identity", "ai_model"
  is_ai_asset: boolean;
  ai_tags?: {
    model_name?: string;
    endpoint_publicly_reachable?: boolean;
    vector_store?: boolean;
    agent_deployment?: boolean;
  };

  // Security posture
  is_public: boolean;
  attached_policies: string[];
  network_exposure: "public" | "vpc" | "private";
  encryption_at_rest: boolean;
  encryption_in_transit: boolean;

  // Graph edges
  trust_relationships: TrustEdge[];   // who can assume/access this
  data_classification: "public" | "internal" | "sensitive" | "regulated";
  last_assessed: string;
}
```

Every asset is normalized to this schema regardless of provider. The `is_ai_asset` flag and `ai_tags` are the AI-specific layer that makes the harness work for AI-heavy estates. A model endpoint that is publicly reachable is a different risk class than a private endpoint serving the same model — the discovery layer tags this at inventory time, not at assessment time.

## AI-specific assets to inventory

The cloud asset discovery harness extends to AI-specific assets that native CSPM tools do not always track. These are the assets that an AI-focused adversary targets first:

- **Model endpoints** — SageMaker, Vertex AI, Azure ML. Are they public? What IAM role do they assume? Can they be invoked without authentication?
- **AI agent deployments** — Bedrock agents, OpenAI assistants, custom orchestration. What tools can the agent call? What data can it access?
- **Embedding services** — text-to-vector endpoints. Exfiltration path: query the embeddings to infer training data.
- **Vector databases** — Pinecone, Weaviate, pgvector, OpenSearch. Do they hold sensitive embeddings? Are they publicly reachable?
- **Training data stores** — S3 buckets, Azure blobs holding datasets. These are the crown jewels for an AI-focused adversary.

The discovery harness inventories each with the security-relevant fields. The `ai_tags.endpoint_publicly_reachable` field is the difference between a model endpoint that is an internal tool and one that is a public attack surface.

## The reconciliation loop

Discovery is not a one-shot scan. It is a reconciliation loop that runs continuously:

```
Provider API (current state)
    ↓
Normalize to CloudAsset schema
    ↓
Diff against last-known inventory
    ↓
New assets → tag + assess
Changed assets → re-assess
Deleted assets → mark stale, remove from active graph
    ↓
Updated asset graph
```

The diff against last-known inventory is what catches drift. A new public S3 bucket created at 3 AM is in the inventory by 3:05 AM and assessed by 3:06 AM — not discovered in next month's audit.

## The multi-provider normalization challenge

The reason a harness adds value over native CSPM tools is that real cloud estates are multi-provider. A typical enterprise runs AWS for compute, Azure for identity (via Active Directory sync), and GCP for data analytics. Each provider's native tooling sees only its own estate. The CISO's question — "what is our overall cloud risk?" — cannot be answered by any single provider's dashboard.

The harness solves this by normalizing every resource to the CloudAsset schema, regardless of origin. An AWS S3 bucket, an Azure Blob storage container, and a GCP Cloud Storage bucket all become CloudAsset records with `resource_type: "storage"`, `is_public`, and `data_classification` fields in the same vocabulary. An IAM role in AWS, a managed identity in Azure, and a service account in GCP all become CloudAsset records with `resource_type: "identity"` and `trust_relationships` in a unified format. This normalization is what makes cross-provider attack-path analysis possible — a path from an AWS role through a cross-cloud trust to an Azure data store is visible only when both are in the same graph.

The cost of normalization is maintaining the provider mapping. AWS Config returns resources in the AWS Resource format, Azure Resource Graph returns them as Kusto query results, and GCP SCC returns them in the Cloud Asset format. Each needs a normalizer that maps native fields to the CloudAsset schema. The normalizers are maintained per-provider and tested against sample data. When a provider adds a new resource type, the normalizer is extended — this is ongoing maintenance, not a one-time build.

---

# S09.2 — Attack Path Analysis

A finding is a single point of weakness. An attack path is the chain that turns a single weakness into a breach. A public S3 bucket is a finding. A public S3 bucket that an over-privileged IAM role can write to, where that role is assumable by a Lambda that is invokable from a public API Gateway — that is an attack path. The harness's job is to find the paths, not just the points.

## What the harness must model

The cloud attack surface is a graph. Nodes are assets (resources, identities, data stores). Edges are access relationships (can-assume, can-invoke, can-read, can-write). The attack path is a traversal from an entry point (externally reachable asset) to a high-value target (sensitive data store, privileged identity).

Three edge types dominate cloud attack paths:

1. **IAM escalation edges.** Identity A can assume Role B. Role B has a permission that grants further privilege. The path climbs the privilege ladder.
2. **Public exposure chains.** Service A is publicly reachable. Service A can access Service B. Service B holds sensitive data. The path traverses from outside to crown jewel.
3. **Lateral movement.** A compromised resource in one account or service can pivot to another through trust relationships, shared VPCs, or over-broad security groups.

## Graph-based attack path reasoning

The asset inventory from S09.1 is the graph. The harness builds the graph explicitly and runs path-finding algorithms on it:

```python
from collections import defaultdict, deque

class AttackGraph:
    def __init__(self, assets: list[CloudAsset]):
        self.nodes = {a.asset_id: a for a in assets}
        self.edges = defaultdict(list)  # source_id -> [(target_id, edge_type)]
        self._build_edges()

    def _build_edges(self):
        for asset in self.nodes.values():
            # IAM trust edges: who can assume/access this asset's role
            for trust in asset.trust_relationships:
                self.edges[trust.source_id].append((asset.asset_id, trust.type))
            # Network exposure edges: publicly reachable assets are entry points
            # Data flow edges: compute that can read/write storage
            ...

    def find_paths_to_targets(
        self, entry_points: list[str], targets: list[str], max_depth: int = 8
    ) -> list[list[str]]:
        """BFS all paths from entry points to sensitive targets."""
        paths = []
        for entry in entry_points:
            for target in targets:
                found = self._bfs_path(entry, target, max_depth)
                paths.extend(found)
        return paths

    def entry_points(self) -> list[str]:
        """Externally reachable assets — the attack surface boundary."""
        return [a_id for a_id, a in self.nodes.items()
                if a.network_exposure == "public"]

    def sensitive_targets(self) -> list[str]:
        """Assets holding sensitive or regulated data."""
        return [a_id for a_id, a in self.nodes.items()
                if a.data_classification in ("sensitive", "regulated")]
```

The key design decision is that the graph is built from the asset inventory, not from a separate scan. Discovery and path analysis share the same data model. This means the attack paths update as the inventory updates — drift in the inventory propagates to the path analysis.

## Scoring path severity

Not all attack paths are equal. A three-hop path to a regulated database is more severe than a five-hop path to internal documentation. The harness scores each path by combining CSPM findings with context:

```python
def score_path(path: list[str], graph: AttackGraph) -> float:
    """Score an attack path by severity. Higher = worse."""
    score = 0.0
    for node_id in path:
        node = graph.nodes[node_id]
        # Sensitive target reached? Major weight.
        if node.data_classification in ("sensitive", "regulated"):
            score += 40
        # Public exposure? Entry point weight.
        if node.network_exposure == "public":
            score += 10
        # Over-privileged IAM? Escalation weight.
        if node.service == "iam" and is_over_privileged(node):
            score += 15
        # Missing encryption? Adds risk.
        if not node.encryption_at_rest:
            score += 5
    # Shorter paths are more exploitable
    score *= (1.0 + (1.0 / len(path)))
    return min(score, 100.0)
```

The scoring is deterministic and auditable. A path's severity is a function of what it reaches, how exposed the entry point is, and how short the path is. Context enrichment from threat intelligence (is this IAM pattern a known TTP? is this data class regulated in this jurisdiction?) adds external signals to the internal graph.

## Context enrichment and threat intelligence

The attack path graph is built from internal data — the asset inventory, IAM policies, and trust relationships. But path severity is not purely internal. A public S3 bucket holding marketing images is on a technically valid attack path (public → storage), but it is low severity. The same public bucket holding PCI-regulated cardholder data is critical. The difference is context: what is in the bucket, and what would exposure cost the business?

The harness enriches path scoring with two context layers. The first is data classification — tagged on the asset at inventory time. Sensitive and regulated data stores receive higher path scores. The second is threat intelligence — is the IAM pattern on this path a known adversary TTP? Is the exposure type being actively exploited in the wild? Threat intelligence feeds (MITRE ATT&CK for Cloud, cloud provider threat reports, CISA alerts) provide external signals that adjust the internal path score. A path using a privilege escalation technique that is being actively exploited in current campaigns scores higher than a path using a technique that is theoretical.

The enrichment is additive, not replacing. The base path score comes from the graph (target value, exposure, length). Context enrichment adjusts it. This keeps the scoring transparent — you can explain why a path is critical by pointing to its components: it reaches regulated data (plus forty), the entry point is public (plus ten), the escalation technique is a known TTP being exploited this month (threat intel multiplier), and the path is only three hops (length multiplier).

## Why paths, not findings

A finding list tells you there are 47 misconfigurations. An attack-path list tells you which 3 of those 47 combine into a breach path. The remediation priority is path-derived, not finding-derived. Fixing a single node on a critical path breaks the path; fixing 10 nodes not on any path changes nothing about the actual risk. Attack-path analysis is what turns a 47-item finding list into a 3-item priority list.

---

# S09.3 — Continuous Posture Monitoring

Posture at a single point in time is a snapshot. The cloud is not a snapshot. The drift problem — cloud infrastructure changes constantly and posture degrades silently — is the core operational challenge. A harness that runs a posture assessment once a week catches the breach path that existed at assessment time, not the one that appeared on Tuesday and was exploited on Wednesday. Continuous posture monitoring closes that gap.

## The drift problem

Cloud posture degrades through mechanisms that do not look like attacks:

- **Console drift.** An engineer opens a security group to test connectivity, forgets to close it. The port is open indefinitely.
- **Terraform state divergence.** The IaC declares a private bucket; a manual change makes it public; the next Terraform apply reverts it, but in between it was exposed.
- **Permission creep.** A role accumulates permissions over time as new policies are attached and none are removed. The role started least-privilege; it is now over-privileged.
- **Ephemeral resource exposure.** A Lambda is deployed with a debug environment variable that disables auth, intended to be temporary. It persists.

None of these trigger an alert in a traditional security tool, because none looks like an attack. They are drift. The posture monitor's job is to catch drift before it is exploited.

## Event-driven harness triggers

The continuous posture monitor is event-driven. Rather than polling the entire estate on a schedule, it subscribes to the cloud provider's audit log — AWS CloudTrail, Azure Activity Log, GCP Cloud Audit Logs — and re-assesses only the resources that changed:

```python
from dataclasses import dataclass
from typing import Callable

@dataclass
class CloudEvent:
    event_id: str
    timestamp: str
    provider: "aws" | "azure" | "gcp"
    event_source: str       # e.g. "s3.amazonaws.com", "iam.amazonaws.com"
    event_name: str         # e.g. "PutBucketAcl", "AttachRolePolicy"
    resource_arn: str
    actor_arn: str          # who made the change
    source_ip: str
    raw_event: dict

# Security-relevant events trigger a targeted re-assessment
SECURITY_RELEVANT_EVENTS = {
    # IAM changes — privilege boundary shifts
    ("iam", "AttachRolePolicy"), ("iam", "CreatePolicy"),
    ("iam", "PutRolePolicy"), ("iam", "AssumeRole"),
    # Public exposure changes
    ("s3", "PutBucketAcl"), ("s3", "PutBucketPolicy"),
    ("ec2", "AuthorizeSecurityGroupIngress"),
    # Network exposure
    ("ec2", "ModifyInstanceAttribute"), ("elasticloadbalancing", "ModifyLoadBalancerAttributes"),
    # AI workload changes
    ("sagemaker", "CreateEndpoint"), ("bedrock", "CreateAgent"),
}

def should_reassess(event: CloudEvent) -> bool:
    key = (event.event_source.split(".")[0], event.event_name)
    return key in SECURITY_RELEVANT_EVENTS

async def posture_monitor(event_stream, graph: AttackGraph):
    async for event in event_stream:
        if should_reassess(event):
            # Re-fetch the specific resource's current state
            asset = await fetch_resource(event.provider, event.resource_arn)
            # Re-assess against benchmarks
            findings = assess_against_benchmarks(asset)
            # Update the graph — may add/remove attack paths
            graph.update(asset)
            # Route findings by severity
            route_findings(findings, event)
```

The event-driven design is efficient. The entire estate is not re-scanned on every change. Only the changed resource is re-fetched and re-assessed, and only the affected portion of the attack graph is updated. This scales: an account with 50,000 resources and 10,000 changes per day does not require 50,000-resource scans — it requires 10,000 single-resource assessments.

## Benchmark enforcement

The assessment against benchmarks is what defines "good posture." The harness maps industry benchmark controls to checkable policies:

| Benchmark | Scope | Example control |
| --- | --- | --- |
| **CIS AWS Foundations** | AWS account baseline | "Ensure no security groups allow ingress from 0.0.0.0/0 to port 22" |
| **CIS Azure** | Azure subscription baseline | "Ensure that 'Guest users are not allowed to create security groups'" |
| **CIS GCP** | GCP project baseline | "Ensure that SSH port is not open to all" |
| **NIST CSF** | Cross-framework mapping | Identify → Protect → Detect → Respond → Recover |
| **PCI DSS** | Cardholder data scope | "Restrict inbound/outbound traffic to necessary ports" |

The harness does not implement these benchmarks from scratch — it maps them. The native CSPM tools (Security Hub, Defender for Cloud, SCC) already implement the control checks. The harness's job is to route the findings, correlate them with attack paths, and prioritize remediation. A benchmark finding on a node that is on a critical attack path is remediated before a benchmark finding on an isolated resource.

## Alert triage

Not every policy violation is urgent. The harness scores and routes findings by exploitability and business impact:

- **Exploitability.** Is the vulnerable resource on an active attack path? A public S3 bucket on a path to regulated data is urgent; the same bucket holding public marketing assets is not.
- **Business impact.** What is the data classification of the affected resource? A missing encryption-at-rest on a regulated database is urgent; on a public cache it is not.
- **Reachability.** Is the resource publicly exposed? An open security group on a public-facing load balancer is urgent; on an internal-only subnet it is lower priority.

The triage produces a priority queue, not a flat alert list. Critical findings (exploitable, high impact) page immediately. Medium findings batch into a daily report. Low findings accumulate for the weekly posture review. The harness's discipline is routing — surfacing what matters, suppressing what does not.

## The cost of false positives in posture alerts

Alert fatigue is the silent killer of posture monitoring programs. A CSPM tool that pages on every policy violation — regardless of exploitability — trains the on-call engineer to ignore the pager. When a real critical finding arrives, it is buried in the same channel as fifty low-severity open-security-group warnings. The harness's triage discipline is what prevents this. By scoring every finding against exploitability, impact, and reachability before routing, the harness ensures the page channel carries only findings that genuinely warrant immediate attention.

The rule of thumb: if an on-call engineer receives more than two posture alerts per week that turn out to be non-actionable, the alert channel has failed. The triage pipeline's job is to keep the page channel credible. A finding that pages must be exploitable (on an active attack path), high-impact (affecting sensitive or regulated data), and reachable (publicly exposed or accessible from a compromised identity). Findings that meet all three criteria are rare — perhaps once a week in a well-managed estate. That is the right cadence for a page. Everything else batches.

---

# S09.4 — Reporting and Remediation Workflows

A posture finding that is not reported is invisible. A finding that is reported but not remediated is noise. The reporting and remediation layer is where the harness's output becomes action. This sub-section covers multi-audience reporting, automated remediation, and the approval gate that governs when automated remediation is permitted.

## Multi-audience reports

Cloud security reports serve different audiences with different needs. The harness produces three views from the same underlying finding data:

| Audience | Report type | Content |
| --- | --- | --- |
| **Engineer** | Technical finding list | Each finding: resource ARN, control ID, current config, expected config, remediation command, attack-path membership |
| **CISO** | Executive risk summary | Risk score, trend over time, top 5 attack paths, compliance posture, business-impact framing |
| **Auditor** | Compliance evidence | Control → finding mapping, pass/fail per control, evidence timestamp, assessor identity, retention class |

The reports are generated from the same finding store, not maintained separately. The engineer's finding list, the CISO's risk summary, and the auditor's evidence are views over the same data — they differ in what they project, not in what they store. This ensures consistency: the CISO's "top 5 critical paths" are the same paths the engineer sees in detail, derived from the same graph.

```python
def generate_ciso_report(graph: AttackGraph, history: list, findings: list) -> dict:
    """Generate the executive risk summary from finding data."""
    critical_paths = graph.find_paths_to_targets(
        graph.entry_points(), graph.sensitive_targets()
    )
    top_paths = sorted(critical_paths, key=lambda p: score_path(p, graph), reverse=True)[:5]
    return {
        "risk_score": compute_overall_risk(findings, history),
        "risk_trend": trend_over_last_n_days(history, days=30),
        "top_attack_paths": [
            {"path": p, "score": score_path(p, graph), "target": graph.nodes[p[-1]].resource_type}
            for p in top_paths
        ],
        "compliance_posture": compliance_summary(findings),
        "critical_findings_open": count_by_severity(findings).get("critical", 0),
        "business_impact": business_impact_framing(findings),
    }
```

The CISO report answers one question: is our cloud posture getting better or worse, and what are the three things that matter most this week. It does not list 47 findings. It lists the risk trajectory and the 5 attack paths that most warrant attention.

## Automated remediation

For well-understood issues, the harness proposes and optionally applies the fix. The remediation library covers the common, deterministic fixes:

- **Public S3 bucket** → set `BlockPublicAccess`, update bucket policy.
- **Open security group** → revoke the over-broad ingress rule.
- **Missing encryption** → enable encryption at rest on the resource.
- **Over-privileged role** → generate a least-privilege policy from access analyzer data.

Each remediation is a harness tool: Pydantic-typed input, scope-checked, evidence-logged, and — critically — gated behind an approval decision. The harness can propose a fix in any environment. It can apply a fix only where the approval policy permits.

## The approval gate

Automated remediation in production is not a decision the harness makes alone. The approval gate is mandatory in production and never skipped. The policy defines which environments allow auto-apply and which require human approval:

```python
from typing import Literal

class RemediationPolicy(BaseModel):
    environment: Literal["production", "staging", "development", "sandbox"]
    remediation_type: str
    auto_apply: bool
    requires_approval_from: list[str]  # role ARNs that must approve
    rollback_plan: str                 # how to undo if it goes wrong

def evaluate_remediation(
    finding: Finding,
    policy: RemediationPolicy,
    environment: str,
    approver: str | None,
) -> Literal["propose_only", "apply", "blocked"]:
    # Production ALWAYS requires approval, regardless of policy.auto_apply
    if environment == "production" and approver is None:
        return "propose_only"  # harness generates the fix, does not apply it
    if not policy.auto_apply and approver is None:
        return "propose_only"
    if approver and approver in policy.requires_approval_from:
        return "apply"
    return "blocked"
```

The `production and approver is None → propose_only` line is the load-bearing rule. The harness may generate the exact remediation command for a production finding, attach it to the ticket, and stage it for application — but it does not apply it without a human approval. This is non-negotiable. Automated remediation that breaks a production system is worse than the misconfiguration it was fixing.

## Ticketing integration

Findings route to ticketing systems (JIRA, Linear, ServiceNow) with context that makes them actionable. The ticket contains the resource ARN, the control violated, the current and expected configuration, the attack-path membership, the proposed remediation, and the approval state. The engineer who picks up the ticket has everything needed to act — they do not need to re-investigate the finding.

```python
def create_remediation_ticket(finding: Finding, graph: AttackGraph) -> dict:
    """Create a ticket with full remediation context."""
    on_path = finding.resource_arn in active_attack_paths(graph)
    return {
        "title": f"[{finding.severity}] {finding.control_id} on {finding.resource_arn}",
        "description": render_finding_description(finding),
        "severity": map_severity_to_ticket_priority(finding.severity),
        "attack_path_context": (
            f"Resource is on {len(paths_containing(finding.resource_arn))} active attack path(s)"
            if on_path else "Resource is not on a critical path"
        ),
        "remediation_command": generate_remediation_command(finding),
        "approval_required": finding.environment == "production",
        "rollback_plan": generate_rollback_plan(finding),
    }
```

The attack-path context is the framing that drives prioritization. A finding on a critical path is remediated before a finding of equal severity that is not on any path.

## The remediation library and deterministic fixes

The remediation library contains fixes for well-understood, deterministic misconfigurations — the kind that have a single correct remediation regardless of context. A public S3 bucket that should be private gets `PutPublicAccessBlock`. An open security group rule that allows ingress from `0.0.0.0/0` to port 22 gets the rule revoked. A resource missing encryption at rest gets encryption enabled. These are not judgment calls — the correct fix is known and can be encoded as a harness tool.

The library does not cover everything. Over-privileged IAM roles require judgment — the least-privilege policy depends on what the role actually does, which requires access analyzer data and sometimes business context. Network architecture issues (a resource in the wrong VPC) require architectural decisions. These are proposed as recommendations, not auto-applied. The library covers the deterministic cases; the harness escalates the judgment cases to human review.

Each remediation in the library is a Pydantic-typed harness tool with a rollback plan. The rollback plan is mandatory — every auto-applied fix must have a documented way to undo it if it breaks something. The rollback is tested, not theoretical. For `PutPublicAccessBlock`, the rollback is removing the block. For a revoked security group rule, the rollback is re-adding the rule with the original parameters. The rollback plan is attached to every remediation ticket so the engineer applying the fix knows how to undo it under pressure.

---

## Anti-Patterns

### The snapshot inventory
Running a full asset discovery scan once a week and treating the result as current. Cloud drift makes a week-old inventory stale. Cure: the reconciliation loop (S09.1), event-driven and continuous.

### The finding-only report
Listing 47 misconfigurations with no path analysis. The client sees noise. Cure: attack-path analysis (S09.2) — report the 3 paths that matter, not the 47 points.

### The scheduled-only monitor
Running a posture assessment nightly. The misconfiguration introduced at 9 AM and exploited at 2 PM is missed. Cure: event-driven monitoring (S09.3) keyed off CloudTrail/Activity Log.

### The auto-apply-in-production harness
Automated remediation that applies fixes in production without approval. The fix breaks the production system. Cure: the approval gate (S09.4) — production always requires human approval, propose-only is the default.

---

## Key Terms

| Term | Definition |
| --- | --- |
| **Cloud asset inventory** | Continuously reconciled graph of cloud resources, normalized across providers |
| **Attack path** | Chain of misconfigurations and exposures leading from an entry point to a high-value target |
| **Attack graph** | Explicit graph of assets (nodes) and access relationships (edges) used for path-finding |
| **Event-driven monitoring** | Posture re-assessment triggered by CloudTrail/Activity Log events on security-relevant changes |
| **Drift problem** | Silent posture degradation through console changes, IaC divergence, permission creep |
| **Approval gate** | Mandatory human approval for automated remediation in production; never skipped |
| **Multi-audience report** | Engineer finding list, CISO risk summary, auditor compliance evidence — same data, different views |

---

## Lab Exercise

See `07-lab-spec.md`. Four labs: (1) cloud asset discovery harness with AI workload tagging; (2) attack path analyzer over a cloud asset graph; (3) event-driven posture monitor processing CloudTrail events; (4) CISO report generation with approval-gated remediation tickets.

---

## References

1. **Microsoft Defender for Cloud — AI security posture management.** Discovers AI workloads, builds an AI BOM, tracks externally reachable AI endpoints, maps attack paths across Azure/AWS/GCP.
2. **AWS Security Hub and AWS Config.** Aggregated findings and resource configuration state recording.
3. **Azure Resource Graph.** Kusto-queryable inventory across the entire Azure estate.
4. **GCP Security Command Center.** Asset inventory and posture findings with Premium posture management.
5. **CIS Benchmarks (AWS Foundations, Azure, GCP).** The control sets the harness maps to checkable policies.
6. **S02.2 — Harness tool wrapping.** The discovery tools follow the same Pydantic-schema + scope-check + evidence-logging pattern.
7. **S02.3 — Evidence chain.** Posture findings and remediation actions are logged with the same hash-chained, scope-referenced discipline.
