Local CSPM testing: simulate AWS Foundational Security controls with kumo and TypeScript
securityci-cdaws

Local CSPM testing: simulate AWS Foundational Security controls with kumo and TypeScript

DDaniel Mercer
2026-05-18
24 min read

Use kumo and TypeScript to run Security Hub-like AWS Foundational Security checks locally and fail CI on control drift.

If you want to treat cloud security like software quality—not a quarterly audit—you need a way to test controls before they reach AWS. That’s the core idea behind local CSPM testing: spin up an AWS service emulator like kumo, create representative resources, and run TypeScript assertions that behave like a lightweight version of Security Hub. Done well, this gives you fast feedback in CI/CD, catches control drift early, and makes security checks repeatable for every pull request. For teams already investing in local emulators and hybrid workflows, the pattern feels familiar: simulate the dependency, define expected state, and fail the pipeline when reality diverges.

This guide shows how to build that workflow for AWS Foundational Security Best Practices. We’ll ground the approach in AWS Security Hub’s control model, use kumo as the local AWS substrate, and write TypeScript-based assertions that can be versioned alongside infrastructure code. The result is not a replacement for Security Hub in production, but a practical dev-time safety net—similar in spirit to how teams use emulators for experimentation before hitting a real service boundary.

What Local CSPM Testing Actually Solves

Security Hub is great in AWS, but it is not a fast feedback loop

AWS Security Hub’s Foundational Security Best Practices standard continuously evaluates resources against best-practice controls and surfaces deviations in a centralized view. That is invaluable in production, but it still leaves a gap between authoring infrastructure and discovering a misconfiguration. If a developer accidentally ships an S3 bucket policy, IAM condition, or logging setting that violates a control, waiting until post-deploy scanning means the bug already escaped into your account. Local CSPM testing closes that gap by moving the check left into development and CI.

The value is similar to other forms of infrastructure testing: you validate intent before it becomes operational state. Rather than hoping a scanner will notice a drift later, you encode the control as a test that runs whenever code changes. That makes security part of your engineering workflow, not a separate review queue. Teams that have already adopted API integration testing patterns will recognize the advantage: once your assertions are executable, they become a living specification.

Why kumo is a strong fit for CSPM-style tests

kumo is a lightweight AWS service emulator written in Go. According to the project description, it is designed for both CI/CD testing and local development, requires no authentication, ships as a single binary, supports Docker, and can persist data via KUMO_DATA_DIR. Those traits are ideal for policy-style testing because your suite can start quickly, create mock resources, and tear down without external dependencies. In practice, that means you can run a full security posture check on every commit without provisioning real AWS infrastructure or waiting for service APIs to respond.

Another important point is breadth. kumo supports many AWS services, including IAM, S3, DynamoDB, Lambda, CloudWatch, CloudTrail, Config, CloudFormation, and more. For foundational security checks, this breadth matters because controls often span multiple domains: identity, logging, encryption, network exposure, and auditability. You do not need full parity with AWS to get value; you need enough coverage to model the misconfigurations your team actually makes. That is the same reason practical hybrid workflows often outperform theoretical perfection.

What this pattern is—and what it is not

Local CSPM testing is not a full replacement for AWS Security Hub, AWS Config, or third-party cloud security posture management tools. Those systems observe real cloud state, historical drift, and account-level context that your local environment cannot fully reproduce. However, local testing is excellent at validating the resource definitions you generate from Terraform, CDK, Pulumi, or custom scripts. In other words, it answers the question: “Would this configuration pass a known security control if deployed as written?”

This is a high-leverage question because many security failures are deterministic. If a control requires encryption at rest, public access disabled, or logging enabled, your code can check for that immediately. It is similar to how teams use security-forward workflow design to prevent sensitive mistakes before they spread. The biggest win is cultural: security checks become cheap enough to run everywhere.

Mapping AWS Foundational Security Controls to Local Assertions

Start with a small control catalog, not the entire standard

AWS Foundational Security Best Practices includes a broad set of controls across services like IAM, S3, APIGateway, RDS, ECS, CloudTrail, and more. Do not try to recreate every control on day one. Instead, choose a subset that represents the most common risks in your organization: public exposure, missing logging, weak identity configuration, and unencrypted storage. This gives you a usable baseline while keeping the implementation simple enough to maintain.

A good way to think about it is as a control translation layer. Security Hub controls are written for cloud operations; your local tests should translate them into resource assertions on the objects produced by your infrastructure code. The output can be very specific—such as “S3 buckets must block public ACLs” or “CloudTrail-like audit events must be enabled”—even if the underlying emulator is not a perfect mirror of AWS. For teams that like structured experimentation, the approach resembles the deliberate validation mindset in spacecraft testing: simulate the conditions, measure the failure modes, and only then declare readiness.

Examples of controls worth emulating locally

You can get strong security coverage by targeting controls that are easy to express and high-value to enforce. For example, IAM-related checks can verify least privilege basics, S3 checks can ensure public access is disabled and encryption is configured, and logging checks can confirm that audit trails or access logs are present. If your application uses API Gateway, you can assert that authorization is defined and that execution or access logging is enabled. These checks map closely to common AWS Foundational Security expectations, even if you are implementing them as local invariants rather than direct Security Hub evaluations.

For a broader security architecture perspective, it helps to understand how controls interact. A bucket that is private but unencrypted is still weak. An API that requires auth but emits no logs is difficult to investigate. A service that encrypts data but lacks access auditing can fail incident response requirements. Thinking in combinations, not isolated settings, is what turns a test suite into a genuine CSPM-like tool. That mindset aligns with the same operational discipline seen in workflow systems that optimize for repeatability rather than one-off success.

Use a control matrix to keep scope realistic

Below is a practical way to prioritize your first controls. The goal is to cover the most common drift patterns and the checks that are easiest to automate locally. You can extend the matrix over time as your teams add services or as audits highlight new risk areas. Keep the rules explicit, because ambiguous tests are hard to trust and even harder to debug.

Control areaLocal assertionWhy it mattersTypical drift symptomSuggested priority
S3 public accessBucket blocks public ACLs and policiesPrevents accidental data exposureDefaults or sample code leave bucket publicHigh
Encryption at restStorage resources declare encryptionReduces risk from stolen disks or snapshotsDevelopers omit encryption flagsHigh
LoggingAudit/logging is enabled for key servicesSupports detection and forensicsNon-production configs skip logsHigh
IAM least privilegeRoles avoid wildcard actions/resourcesLimits blast radiusTemporary policies become permanentHigh
API authRoutes or methods require authBlocks anonymous accessDemo endpoints ship without guardsMedium
Secrets handlingNo hard-coded secrets in configAvoids credential leakageLocal test fixtures leak real valuesHigh
Network exposurePublic endpoints are intentional and documentedPrevents accidental internet exposureDefault route becomes publicMedium

How to Build the Local Testing Stack

Spin up kumo as a deterministic test dependency

For local CSPM testing, kumo should behave like an always-available local AWS endpoint. You can run it as a binary on a developer laptop or inside Docker in CI. The important part is determinism: every test run should start from a known state, create the same fixtures, and evaluate the same control logic. If your team values fast and portable infrastructure, this mirrors the appeal of performance-focused local tooling: remove unnecessary overhead so the useful work happens sooner.

Use separate environments for test initialization and assertion execution. A common pattern is to start kumo, seed resources through the AWS SDK v2-compatible interface where applicable, and then run your TypeScript test suite against those resources. When the suite ends, wipe the data directory or reset the container. This makes failures reproducible and prevents state bleed between runs. If you enable persistence with KUMO_DATA_DIR, use it intentionally for debugging—not as a default in CI.

Structure your TypeScript project around security controls

TypeScript is ideal for this workflow because it gives you typed helpers, readable test contracts, and a single language for resource fixtures and assertions. Organize your code into three layers: resource builders, control checkers, and test runners. Resource builders create the minimal AWS-like objects you need, control checkers encode the policy logic, and test runners translate results into CI pass/fail signals. This separation keeps tests maintainable as your control catalog grows.

You will also want a clean way to model “resource snapshots.” Instead of asserting directly against raw SDK responses everywhere, normalize them into domain objects such as BucketState, RoleState, or ApiStageState. That gives you one place to handle naming conventions, optional fields, and emulator quirks. The same principle underpins robust integration adapters: normalize messy inputs before applying business rules.

A practical repository layout

A useful layout might look like this: /fixtures for seed data, /controls for reusable rule functions, /tests for scenario-based checks, and /scripts for local bootstrap tasks. Keep your control functions pure whenever possible so they are easy to unit test without starting kumo. Then add a smaller number of end-to-end tests that prove the emulator wiring works. This layered approach gives you the speed of unit tests and the realism of integration tests.

One advantage of TypeScript is that it can encode the shape of each control result. For example, a failing control can return the control ID, impacted resource, severity, and remediation text. That makes the test output actionable instead of cryptic. It also prepares your local checks for future reporting integrations, whether that’s Slack, GitHub Checks, or a security dashboard. If your team is exploring broader automation, the logic echoes patterns from agentic assistants for workflow automation, where typed steps reduce failure ambiguity.

TypeScript Patterns for Security Assertions

Define controls as pure functions

The cleanest pattern is to make each control a pure function that accepts a resource snapshot and returns a pass/fail result. Pure functions are easy to unit test, easy to compose, and easy to reuse across services. They also make it obvious which inputs matter for a given rule. That clarity is valuable when your tests are used to enforce policy across multiple teams.

Example:

type ControlResult = {
  controlId: string;
  passed: boolean;
  resourceId: string;
  reason?: string;
};

type S3BucketState = {
  name: string;
  publicAccessBlocked: boolean;
  encryptionEnabled: boolean;
};

export function checkS3PublicAccess(bucket: S3BucketState): ControlResult {
  const passed = bucket.publicAccessBlocked;
  return {
    controlId: 'S3-PUBLIC-ACCESS',
    passed,
    resourceId: bucket.name,
    reason: passed ? undefined : 'Bucket allows public access'
  };
}

This small shape scales surprisingly well. Once you write 10 to 20 of these helpers, you can create a catalog that resembles a mini Security Hub for your product teams. The only difference is that the catalog is test-driven and local-first. That’s an important distinction, because it means your rules evolve with the codebase instead of lagging behind it.

Use parameterized scenarios for control drift

Control drift often hides in “mostly correct” configurations. A test suite should therefore include parameterized scenarios that simulate the kinds of mistakes developers actually make: an encryption flag missing, a log destination omitted, a public ACL reintroduced, or an auth route left open. For each scenario, assert not just that the control fails, but that it fails for the right reason. That will save you from false confidence when the emulator or infrastructure code changes.

In TypeScript, table-driven tests are especially effective here because they make the control catalog compact and readable. Each scenario can include the input snapshot, expected result, and remediation note. This is the software equivalent of a strong checklist, much like the planning discipline behind high-clarity operational checklists: fewer surprises, better outcomes, more consistent execution.

Turn failures into remediation guidance

A control that merely says “fail” is not enough. Developers need to know what to fix and why the rule exists. Include a remediation string that points to the configuration change, the relevant IaC setting, or the owning team’s standard. Over time, these messages become as valuable as the tests themselves because they reduce back-and-forth between application engineers and security reviewers.

Pro Tip: Write every control message as if it will be pasted into a pull request comment. If the developer can fix the issue from the message alone, your test is doing real work. If not, it is just generating noise.

Example Workflow: From Resource Creation to CI Failure

Seed local resources in a repeatable way

A useful workflow starts with a bootstrap script that launches kumo, creates the resources your service would normally deploy, and stores a snapshot of the resulting state. That script should be deterministic: same inputs, same outputs, no external secrets. If you are using Terraform or CDK, you can generate the intended settings from those manifests and then assert against them locally. The point is not to exactly reproduce AWS internals; it is to verify that the resource intent matches your security baseline.

Because kumo is lightweight and easy to distribute, you can run this bootstrap both on developer machines and in CI. That consistency is what makes the pattern valuable. When the same test suite runs in both places, developers catch issues before review, and CI acts as the final gate. The deployment pipeline becomes a security enforcement point rather than a passive delivery system. If your organization is already doing structured operational testing, this complements broader patterns found in resilient data architectures.

Fail CI on control drift with explicit exit codes

Your CI job should treat any failed foundational control as a build failure unless the test is explicitly marked informational. Do not bury failures in logs or require manual inspection. Emit a concise summary showing the control ID, affected resource, and the remediation hint, then exit non-zero. This makes the security signal unambiguous and actionable, which is exactly what infrastructure testing is supposed to do.

In practice, teams often split tests into tiers: blocking controls for high-risk issues and advisory controls for lower-priority checks. That gives security and platform teams room to evolve standards without blocking every experiment. It also mirrors good product governance in other domains, like the structured decision-making used in marketplace quality control, where not every rule has the same business weight.

Log results in a format humans and machines can read

If your suite grows beyond a few checks, consider generating structured output such as JSON or JUnit XML in addition to console text. This allows CI to annotate pull requests, trend failures over time, and feed results into dashboards. Security automation becomes far more useful when the output is machine-readable, because other systems can make decisions on top of it. A failure should be visible to developers immediately and still available to platform and security teams later.

Think of the output contract as part of the product. Clear output reduces alert fatigue and helps teams trust the checks. The same idea shows up in high-quality operational tooling everywhere, including systems designed to turn operational signals into decisions, like data-layer-driven operations. Without a structured layer, the automation is much less valuable.

Practical Examples of Controls You Can Implement Today

S3: public access, encryption, and ownership hygiene

S3 is often the easiest starting point because misconfigurations are common and the control logic is straightforward. Your test can assert that public access is blocked, encryption is enabled, versioning is configured where required, and bucket policies do not grant broad anonymous access. Even in a local emulator, these checks help teams validate the security assumptions embedded in their storage design.

For teams with many data flows, use a standard bucket profile so the test can compare expected and actual settings. This reduces drift because developers do not reinvent secure settings per feature. The habit is similar to setting up guardrails in other operational areas, such as the repeatable process design in regulatory inventory workflows: when the baseline is clear, exceptions stand out immediately.

IAM: wildcard detection and role boundary checks

IAM controls are more nuanced, but the same local pattern still works. Check for wildcard actions and resources, ensure that sensitive roles do not have broad admin policies, and validate that trust relationships are intentional. You do not need to implement every AWS IAM nuance to create useful enforcement; even a narrow rule set catches many accidental privilege escalations. Over time, you can add exceptions for known service roles and tightly scoped automation permissions.

This is where TypeScript’s expressive types help. You can model policy statements, evaluate them with helper functions, and keep the assertions readable even as the rule set grows. Strong typing also makes refactoring safer when your infrastructure patterns change. That’s particularly useful in fast-moving teams where a configuration file may be edited by several developers in one week.

API Gateway and logging: auth is not enough

APIs often fail security reviews not because they lack authentication, but because they lack observability. A local CSPM test can assert that routes require authorization and that logging/tracing settings are enabled. If your application exposes multiple stages, make sure the default stage and any internal stage both pass the same control logic. It is very common for the “demo” or “preview” stage to lag behind the production posture.

Logging controls are especially valuable because they support detection and incident response, not just compliance. If a service behaves badly, logs are what help you reconstruct the event. That makes these tests practical, not ornamental. For more on disciplined observability design across systems, see the habits behind high-availability communications platforms.

Building a CI/CD Pipeline That Catches Security Regressions

Run the tests as part of pull request validation

The best place for local CSPM tests is the same place where code already gets validated: pull request CI. When a change alters Terraform, CDK, or other infrastructure code, the pipeline should launch kumo, apply the intended configuration, and run the TypeScript control suite. If a control fails, the PR should fail before merge. This keeps security issues from becoming release-day surprises.

To make the signal strong, keep the suite quick. A fast suite gets run consistently; a slow one gets bypassed. That is why kumo’s lightweight design matters so much. Teams that prioritize quick feedback often see better adoption because the tests feel like part of the developer workflow rather than a special security ceremony. This is the same reason fast-moving teams invest in streamlined, low-friction systems like compact review formats that keep momentum high.

Add policy exceptions only with expiration

Occasional exceptions are inevitable. A control may be temporarily waived for a migration, an experimental feature, or a third-party dependency constraint. When that happens, encode the exception in code with an expiration date and owner. Do not rely on tribal knowledge or a ticket that nobody revisits. Exceptions without expiry become permanent risk.

From a governance perspective, this is the difference between manageable debt and security rot. A local test suite makes exceptions visible because they must be represented explicitly. That visibility is valuable even when the exception is accepted. It forces a conscious decision, which is usually the right outcome for security controls.

Once your suite is in CI, you can start analyzing patterns: which controls fail most often, which teams introduce the most regressions, and which rule messages generate the most questions. Those signals are valuable for platform engineering and security enablement. They help you prioritize education, documentation, and automation investments. If a rule fails constantly, maybe the default scaffold needs improvement rather than more reminders.

That kind of operational feedback loop is exactly what mature automation should do: not just block bad changes, but tell you where the system design is brittle. Teams that treat security tests as product telemetry often improve both code quality and developer experience. The broader lesson is consistent with the design of reliable systems everywhere, from technology risk management to deployment governance.

Common Pitfalls and How to Avoid Them

Do not chase perfect AWS parity

The biggest mistake teams make is assuming local CSPM testing must perfectly reproduce AWS behavior. It does not. The goal is not to emulate every edge case of every service; it is to create a stable, fast signal for the controls that matter most to your codebase. If you demand full parity, you will spend too much time on emulator fidelity and not enough on security coverage.

Instead, focus on high-value invariants. Model the checks that are deterministic, repeatable, and meaningful to your developers. Leave account-wide context, detective controls, and runtime-only signals to production tools like Security Hub and AWS Config. That division of labor is what makes the overall system credible and maintainable.

Do not let tests become unreadable policy spaghetti

Security tests can become difficult to maintain if every rule is hand-coded in a different style. Use shared utilities, typed models, and a consistent result format. Keep each rule small, named, and documented. If a new engineer cannot understand the control in a minute or two, the suite is too opaque.

Readable tests also make security reviews easier. When a reviewer can trace a rule from fixture to assertion to remediation message, confidence goes up. The test suite becomes a practical artifact that engineers trust instead of a mysterious gate they fear. Good structure matters here more than cleverness.

Do not forget production validation

Local testing is an early warning system, not the final security verdict. You should still run AWS-native posture checks in real accounts and incorporate cloud runtime detection where appropriate. Security Hub, AWS Config, and related tools give you the authoritative view of deployed resources. Local CSPM testing simply reduces the time between code creation and security feedback.

That layered model is usually the most effective. Development tests catch obvious errors early; production scanners catch real-world divergence; governance processes handle exceptions and trends. Together they create a defense-in-depth workflow that scales with your team. Think of it as the security version of a well-run build pipeline: each layer has a clear job, and none is expected to do everything.

Implementation Checklist and Adoption Strategy

Start with one service and three controls

If you are introducing this pattern to a team, start small. Pick one service such as S3 or IAM, define three controls that directly map to recurring mistakes, and ship the first tests in CI. Measure how often the suite catches issues and whether the output is understandable. If developers fix problems quickly, expand to the next service. Small wins build trust.

At this stage, your goal is not coverage percentage. It is operational credibility. A short, reliable suite that catches real drift is worth far more than a giant catalog nobody runs. That principle mirrors strong product launches in many fields: ship the useful core first, then extend with confidence. If you need a model for that incremental discipline, look at how teams use security-forward design patterns without overcomplicating the user experience.

Document controls as engineering standards

Every test should correspond to a documented standard: what it checks, why it exists, and how to fix it. Keep that documentation close to the code, ideally in the same repository. This turns the suite into a living policy handbook for engineers. The more obvious the rationale, the less resistance you will get when a build fails.

When documentation and tests reinforce each other, adoption improves. Developers learn the rules by encountering them in the same place they work, and security teams spend less time explaining basics. The suite becomes part of onboarding, code review, and incident prevention all at once.

Expand using risk, not novelty

When the initial controls are stable, expand based on risk rather than on how many AWS services kumo supports. Add controls where the blast radius is largest, where your organization has recurring incidents, or where audits repeatedly surface gaps. That keeps the suite relevant and prevents it from becoming a zoo of low-value rules. The best security automation is boring in the right way: it consistently blocks the mistakes that actually happen.

As your org matures, you can connect local CSPM results to policy-as-code, review workflows, and production guardrails. The local suite then becomes one node in a larger security automation network. That is the real endgame: not just scanning infrastructure, but making security drift difficult to introduce in the first place.

Conclusion: Make Security Hub-Style Thinking Part of Development

Local CSPM testing with kumo and TypeScript gives teams a practical way to simulate AWS Foundational Security controls before code reaches the cloud. It is fast, portable, and easy to embed into CI/CD, which makes it a strong companion to production tools like Security Hub rather than a replacement for them. By modeling a handful of high-value controls as executable assertions, you turn security posture into a developer-visible contract. That reduces drift, improves remediation speed, and makes the secure path easier to follow.

If you adopt just one idea from this guide, make it this: security controls should fail as early as possible, in the smallest environment possible, with the clearest possible explanation. That is how local testing becomes a real security control, not just a convenience. And once your team gets used to it, you may find that the fastest way to improve cloud security is not more scanning—it is better tests.

For additional perspectives on workflow design, testing discipline, and secure systems thinking, explore our broader engineering library, including hybrid workflow patterns, integration testing strategies, and secure development workflow guidance.

FAQ

What is local CSPM testing?

Local CSPM testing is the practice of validating cloud security controls before deployment by running policy checks against emulated or locally modeled resources. Instead of waiting for a cloud posture tool to find a problem in AWS, you fail earlier in development or CI. This makes security feedback faster and more actionable.

Why use kumo for AWS security testing?

kumo is lightweight, easy to run locally or in Docker, and designed for CI/CD and local development. Because it supports a broad set of AWS services and requires no authentication, it is well suited for reproducible infrastructure and security tests. Its speed and simplicity are especially helpful for pull request validation.

Can local tests replace AWS Security Hub?

No. Local tests are best used to catch configuration mistakes before deployment, while Security Hub remains valuable for scanning real AWS accounts and workloads. The strongest approach is layered: local tests for shift-left validation and Security Hub for runtime and account-level posture.

What AWS Foundational Security controls are easiest to emulate locally?

Controls that check deterministic configuration are usually the easiest: S3 public access, encryption settings, IAM wildcard use, API logging, and auth requirements. These are straightforward to encode as assertions because they map cleanly to resource properties.

How should CI fail when a control drifts?

CI should fail with a non-zero exit code and show the exact control, resource, and remediation message. Avoid burying the issue in verbose logs. A good failure message should help a developer fix the problem immediately.

How much TypeScript do I need for this pattern?

Not much beyond solid test structure, typed resource models, and a few helper functions. TypeScript shines here because it helps you keep control definitions readable and refactorable as your rule catalog grows.

Related Topics

#security#ci-cd#aws
D

Daniel Mercer

Senior Technical Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-05-20T20:43:35.982Z