Writing Security Invariants Got Easy. Keeping Them True Didn't.
In 2022, Alex Smolen published a short essay arguing that a security programme should be built from invariants. The first candidate on his list was “all software services use single sign-on”. Four years on, a model will write that check for you in the time it takes to read this sentence. The check was never the hard part.
An invariant, in Smolen’s sense, is a security property you can state without argument and verify without interpretation. His defining line is the one worth remembering: “It may not be true everywhere, but we know everywhere where it isn’t true.”
That last clause is the whole point. An invariant is not “zero failures”. It is zero unexplained failures. He gave four steps to get there: identify every resource it applies to, measure whether each one adheres, document the exceptions, and prevent regressions.
Smolen did not coin the term. AWS was using it in public by 2019: an Amazon paper on its Tiros reasoning tool describes checking “adherence to security invariants for many AWS services”, and AWS guidance describes the guardrails set by service control policies as security invariants. That lineage treats an invariant as a guardrail or a proof. Smolen’s adds what this post is about: the exceptions, and a programme to reach zero unexplained.
I have argued for invariants before, inside an AI-driven delivery pipeline. This post is about running them across a whole estate, as a security operations practice: what the four steps look like in 2026, which engine I would pick, and the small seed I would plant to grow the rest. It is written for the security lead who would have to run it.
Smolen’s four steps, re-run in 2026
Identify. Inventories are queryable tables now. But the estate grows things nobody registers: AI tools bought on a card, OAuth grants, agents acting under someone’s credentials. So coverage is a number you measure — “evaluated X of the Y we know about” — never a claim.
Measure. This is the step that got easy. Smolen later argued that security policies “aren’t just documents — they’re data”. Once a policy line is data, a model can turn it into a query against your inventory. But on real enterprise data warehouses, BEAVER found state-of-the-art agentic frameworks on GPT-5.2 “achieve only 10.8% accuracy”. The model writes the check; it never is the check. Every invariant ships with two fixtures: a known-bad example that must turn it red, and a known-good one that must stay green. A check you have never seen fail is not known to work. Watari taught me that the expensive way.
Document exceptions. Smolen sorts every failing resource into two buckets: “we should resolve this issue”, or the invariant “doesn’t apply to this resource”. His first bucket already allows a backlog. What it does not hold is the case every estate I have worked in carries: the invariant applies, it should be fixed, and it cannot be — yet. The vendor does not offer SSO. The app retires in March. The SSO tier costs several times the base price — sso.tax has catalogued that markup for years. That case needs a reason and a date, and today’s default tools give it neither.
In AWS Security Hub CSPM, a suppressed finding means “you reviewed the finding and do not believe that any action is needed”; the note is optional, and nothing expires. Some commercial tools do better: Sysdig Secure lets you “define how long the exception remains active”, and “after the expiration date, the risk is evaluated again”. So the clock is buildable, and buyable. It is just not the default.
Prevent regressions. Evaluating on every change is cheap now. The pressure moved the other way: agents change infrastructure faster than anyone reviews it.
Borrow detection engineering
Detection engineers solved the shape of this problem years ago. Sigma
lets you write one portable rule in YAML and convert it for many SIEMs. Its processing pipelines
“let you map field names, point rules at the right index or log stream, and apply
environment-specific tweaks — all without touching the original rule.” A rule carries a lifecycle
status — experimental, test, stable — framework tags, and known false positives. And Sigma
Filters keep exclusions outside the rule.
I built Secrust, a Sigma correlation engine over OCSF events, so I know this architecture from the inside. Most of it transfers. One thing does not: Sigma matches events, and an invariant judges state. “Did this happen?” is not “is this true of every resource right now?” So I borrow the architecture, not the rule language. The closest prior art I found is cnspec, whose policies use one query language, MQL, across cloud, Kubernetes, SaaS and infrastructure as code. I found no portable, Sigma-style format for posture state.
Two pieces matter most. The lifecycle maps onto earning the right to block: experimental means monitor, test means alert, stable means enforce. And Sigma keeps exclusions outside the rule, as data — but, like every default in the grid above, with no clock and no reason.
The engine: SQL over a synced inventory
The engine I would pick is unglamorous. An invariant quantifies over every resource, which is a set query, and SQL’s native strength. The anchor example — every SaaS application behind SSO — needs the identity provider’s app catalogue, OAuth grants and spend data, joined. And tracking needs history. CloudQuery is built to “sync your cloud infrastructure metadata into your data warehouse”, so results persist.
Each invariant returns three columns — resource, status, reason — the contract Powerpipe uses for its controls. Results leave as an OCSF Compliance Finding, into the same SIEM pipeline as your detections: a security operations practice, not a compliance spreadsheet.
Engines that can block or fix become secondary targets of the same spec. The strongest is a preventive guardrail: AWS service and resource control policies make an invariant hard to break across a whole organisation. Hard to break is not impossible — AWS’s own data perimeter guidance notes that SCPs “do not apply” to service-linked roles or AWS service principals. So even a guarded invariant keeps its check. Then OPA for infrastructure as code; Kyverno at Kubernetes admission; Cloud Custodian and AWS Config where change-triggered evaluation or remediation is possible. Stop everything you can at the plan; it is the cheapest place. But infrastructure as code checks the plan, and an invariant checks the territory. Console changes, break-glass sessions, agents at runtime, and a SaaS vendor’s SSO setting never pass through a plan. So compile to both. A runtime red for something you enforce in code then tells you about a path that bypasses your code.
Two speeds fall out of the same split. The warehouse syncs on a schedule: it tracks and proves. The change-triggered backends catch the public bucket. The two fixtures run against every target.
The seed
The seed is three small files and one contract. Everything else grows from them. Here are the two that matter most, for the anchor. They are illustrative sketches of my own, not a published format.
# invariants/saas-sso.yaml — one invariant, borrowing Sigma's fields
id: inv-saas-sso-001
title: Every application provisioned through our IdP, or granted OAuth access, enforces SSO
status: experimental # experimental → test → stable = monitor → alert → enforce
policy: POL-IAM-04 # every invariant traces to one policy statement
threat: > # in plain language: what goes wrong if this is false
A phished or reused password opens an app holding our data, because the app never went
through our IdP — so it may skip MFA and survive offboarding.
resource: saas_application # abstract type; the environment mapping says which table
holds: sso_enforced
exception_classes: [vendor_has_no_sso, sso_tier_not_purchased, retiring]
fixtures:
must_fail: an app with OAuth access to our workspace and no SSO
must_pass: an app provisioned through our IdP with SSO enforced
owner: identity-team
# exceptions/legacy-crm.yaml — data, reviewed like code, never edited into the rule
invariant: inv-saas-sso-001
resource: legacy-crm
state: cannot_fix_yet # or out_of_scope: Smolen's "doesn't apply"
class: retiring
compensating: inv-crm-ip-allowlist-002 # itself an invariant, so it can turn red too
accepted_by: head-of-sales # the risk owner, not security
expires: 2027-03-31 # checked in the query: the row turns red again by itself
The third file is the environment mapping: which table, in this estate, is a saas_application.
The contract is resource, status, reason. Here is one clear answer for each part of the programme.
Policies. Every invariant traces to a policy statement, and names in plain language the threat it answers. That is the order an AWS re:Inforce 2022 session taught: threat model, then invariant, then check. Plain language pays twice. A person can judge whether the invariant really answers the threat. And the same sentences can brief a coding agent: AWS’s guidance for AI coding agents is to “encode security invariants as natural-language constraints in a steering document”, then check with deterministic gates. One spec, three renderings: the check, the guardrail, and the agent’s brief.
A model drafts the spec and its SQL from the policy statement. The person who approves it does not need to read SQL: they check the two fixtures. “This app has OAuth access and no SSO: does it turn red? This one has SSO: does it stay green?” That is a review anyone who owns the policy can do — Smolen wanted invariants to be a lens “useful for both the non-security and security teams”. Expertise moves from writing rules to refusing vague ones. My first draft of the anchor said “every SaaS application holding company data”. Who decides what holding company data means? His advice is to “reformulate until the answer is determinable” — hence the version in the spec, which anyone can check.
Campaigns. A campaign takes one invariant across one scope, from test to stable. The first red count is a backlog owned by a team, not a queue of alerts: nothing pages during a campaign. Every red must end in one of three fates.
The campaign is done at zero unexplained reds, which is exactly Smolen’s definition of an established invariant. Then you “pass go, collect $200, and move to the next prioritized problem”. From that point a red only appears when something changes, so it is routed to SecOps as an incident. Pick invariants whose reds have known owners and cheap fixes first. Keep one campaign in flight.
Tracking. Store every evaluation as (invariant, resource, status, reason, time). Watch unexplained reds trend to zero, coverage, exception age and renewals, and time to fix.
Visualisation. Three views. The grid: invariants against environments, amber with a countdown where an exception runs. A policy coverage map, borrowed from detection teams’ ATT&CK maps: which policy statements have an invariant behind them, and which are still prose. Exception ageing: what expires in the next thirty days. The last two get leadership to follow — and an established invariant is audit evidence: Smolen’s team already used their inventory to “automatically generate our monthly continuous monitoring reports” for FedRAMP.
Exceptions. They live outside the rule, as data. Only cannot fix yet carries a compensating invariant, a named risk owner and an expiry. Expiry is evaluated in the query, so nothing has to remember to delete anything. The risk owner signs, not security: the SSO tax becomes a line the business pays or accepts, with a date on it. PCI allows compensation for “a legitimate and documented technical or business constraint”; the record is that documentation.
What the model does, and doesn’t. It drafts specs and translates them to every backend, the way
sigma-cli converts rules, with the fixtures as the net. It proposes owners and drafts exception
reasons for a human to confirm. It never evaluates.
Who checks the checker
A system you grew yourself needs invariants of its own: every invariant evaluated in the last day, every fixture still turning it red, no exception without an owner and an expiry. And it needs one measure of whether it works. I keep coming back to recall: after each incident, ask whether an existing invariant should have caught it. Misses tell you which threat has no invariant yet. Silent hits tell you what is broken.
Three questions I would still expect
Three objections are hard enough to deserve a straight answer.
“Isn’t this just a CSPM?”
No — and it should not try to be. A posture tool answers what is wrong. An invariant programme answers what “done” means, and who decided the rest. Cloud Custodian or your CSPM can be the engine; the programme is what you run through it. So the useful test is not features. It is five questions to ask whatever you already own:
- Does an exception expire?
- Does it require a reason?
- Is there a state for “applies, but cannot be fixed yet”?
- Can it report zero unexplained failures per invariant?
- Does each rule trace to one of your policy statements?
If yours passes all five, buy, and do not build anything. The grid above says most defaults fail the first two.
“If we allow exceptions, doesn’t that defeat the purpose?”
It would if the purpose were zero failures. It never was. Smolen: “For any resource where the invariant does not hold, we must be able to determine the exception for it.” The binary is about knowledge, not compliance. What defeats it is a failure nobody can explain, or an exception that lives forever with no owner — the default the grid shows. Without exceptions, an invariant shrinks until it is trivially true. The guard rail: if exceptions grow faster than fixes, the invariant is lying. Split it or rewrite it; do not renew it.
“Does any of this matter, when exploits are now the main way in?”
It matters more. The 2026 Verizon DBIR says “exploitation of vulnerabilities is now the most common initial access vector for breaches”, at 31%, with credential abuse down to 13%. But read the next finding: only 26% of known-exploited vulnerabilities were fully remediated, at a median of 43 days.
We do not fail to find them. We fail to close them. “No internet-facing asset carries a known-exploited vulnerability older than N days” is an invariant — a remediation promise made checkable. Smolen’s own list had “all results from a scanner are triaged and resolved”. Exposure is state too: what is reachable from the internet, and whether it is in the inventory at all.
The honest limit: against a zero-day in an exposed service, invariants give you containment and inventory, not prevention. They are one control family — the one that keeps known truths true.
Where this costs you
- Generated checks are wrong more often than they look. Two fixtures per invariant are not optional.
- Expiry turns things red on schedule. That is the point, but somebody must own every renewal, and expiry days will be loud.
- The seed is a maintenance bill. Sync jobs break and APIs change. If you cannot own it, buy a tool that passes the five questions.
- A compensating invariant can be theatre if nobody checks that it actually compensates.
- Coverage is never complete. Shadow SaaS is, by definition, not in your inventory.
Where I would not do this: without an asset inventory — fix that first. Without the people to staff one campaign. And where regulation mandates an evidence format: use theirs, such as NIST’s OSCAL POA&M.
The five I would start with
- Every application provisioned through our identity provider, or granted OAuth access to our workspace, enforces SSO.
- No internet-facing asset carries a known-exploited vulnerability older than N days.
- Every internet-facing service is in the inventory, with a named owner.
- No identity used by automation or agents holds a long-lived static credential.
- Every non-human identity has a named owner, and is removed when its system is decommissioned.
The first is the top of Smolen’s list, sharpened. The second is the DBIR’s lesson. The last two come straight from the OWASP Non-Human Identities Top 10, which ranks Improper Offboarding first and Long-Lived Secrets seventh.
I am turning the rest — the full spec format, environment mappings, the catalogue and the campaign playbook — into a recipe you can hand to a coding agent to grow your own. As GitHub’s Spec Kit puts it, “Specifications don’t serve code—code serves specifications.”
So, did the idea survive?
Better than survived. Writing the checks got easy, so the work moved to everything around them: the inventory, the exceptions, the people who sign them. That is where the seed goes.
Smolen’s line still holds, with one word I would add: you know everywhere it isn’t true — and until when.
Sources
- Security Invariants or GTFO — Alex Smolen, 2022. The definition, the four steps, the two exception buckets and the first candidate list. The programme half of everything here.
- Reachability Analysis for AWS-based Networks — Backes, Cook, Rungta et al., Amazon, CAV 2019. The earliest public AWS use of “security invariants” I found.
- NIS234: Learn to create end-to-end security invariants — AWS re:Inforce 2022 slides. The threat, invariant, check lifecycle.
- IAM policy types and Building a data perimeter on AWS — AWS. SCPs and RCPs as security invariants, and where SCPs do not apply.
- A control framework for AI coding agents — AWS Security Blog, 2026. Invariants as natural-language constraints for coding agents.
- Vulnerability Inbox Zero — Smolen, 2022. FedRAMP reports from the inventory.
- Better security policies — Smolen, 2024. Policies as data.
- AWS Credential Isolation for Local AI Agents — Smolen, 2026. The three-part agent identity problem used in the callout.
- Sigma rules, pipelines and filters — SigmaHQ.
- cnspec — its README. Prior art for one query language across environments. GitHub lists its licence as “Other”; check it before adopting.
- BEAVER — Chen et al., arXiv. Text-to-SQL on real enterprise warehouses.
- CloudQuery — its README, for the sync-to-warehouse model.
- Powerpipe controls — the resource/status/reason contract.
- OCSF Compliance Finding — the output schema.
- OPA, Cloud Custodian — enforcement backends.
- Security Hub CSPM, AWS Config, Kyverno, Sysdig Secure — each tool’s own documentation, for the exception grid.
- 2026 Data Breach Investigations Report — Verizon. Exploitation and remediation figures. Convenience-sampled, as it says itself.
- OWASP Non-Human Identities Top 10 — 2025 ranking used in the starting five.
- PCI SSC on compensating controls — 2022.
- sso.tax — the SSO price markup. Many entries date from 2019–2020.
- Spec Kit — GitHub. Spec-driven development.
Both lineages of the concept — AWS’s and Smolen’s — the tool behaviours, and every quotation are sourced and linked; the exception grid reflects each vendor’s documentation as of October 2026 and will date. What is mine: the choice of SQL as the system of record, the seed and its two YAML sketches (illustrative, not a published format), the third exception state, “a campaign ends at zero unexplained reds”, the two-fixture rule, the five questions for vendors, and the five starting invariants. I have not found a portable, Sigma-style format for posture state; if one exists, I would like to hear about it. Disclosure: I work at AWS. Every AWS source cited is public, and nothing here describes Amazon’s internal practice.