Invariant
SMT-based guardrail SAST for AWS Service Control Policies (SCPs) and Resource Control Policies (RCPs). Instead of pattern-matching JSON, Invariant hands your policies to the Z3 theorem prover and asks a precise question:
Is there any
(action, resource, principal)request that used to be denied, but isn't anymore?
If Z3 finds one, that's a guardrail regression, and you get a concrete counterexample instead of a vague warning.
Why
Checkov, cfn-nag, and friends are great at "does this statement look
dangerous," but they can't tell you whether an edit to a Condition block
quietly widened who a Deny statement applies to. That requires reasoning
about the full set of requests a policy allows, not just its syntax. Invariant
answers that question with a solver instead of a regex.
Two checks, one engine
-
invariant scan— baseline check. Point it at a directory and it proves eachservice-control-policies/*.json/rcp-policies/*.jsonfile still upholds the security properties of the default AWS Landing Zone Accelerator (LZA) guardrails, a copy of which ships inside the package (src/invariant/baseline/). No reference file, no manifest, nothing to set up — this is the "just run it" entry point. -
invariant regression --manifest manifest.json— diff check. Proves each candidate still denies everything its own paired reference denied. Use this when you want to check against something other than the bundled LZA defaults — your org's own last-known-good policies, an upstream release, whatever you supply.build_manifest.pyderives the pairing from a git diff so you don't hand-maintain it (see CI integration).
Both share the same check registry, output formats, and exit-code
convention: 0 = no findings (or --soft-fail), 1 = a check failed.
Install
Requires Python 3.10+. z3-solver is installed automatically as a
dependency.
# from a package index, once published
pip install invariant
# or straight from source
git clone https://github.com/YOUR_ORG/invariant.git
cd invariant
pip install .
Quickstart
From the root of your AWS LZA config repo (wherever your
service-control-policies/ and rcp-policies/ directories live):
invariant scan
[PASS] INV_AWS_001 service-control-policies/lza-core-guardrails-1.json No regression found.
[PASS] INV_AWS_001 service-control-policies/lza-quarantine.json No regression found.
[FAIL (critical)] INV_AWS_001 service-control-policies/lza-quarantine.json Candidate no longer denies a request the reference denied: {'action': '""', 'resource': '""', 'principal': '""'}
A FAIL line means Z3 found a concrete (action, resource, principal)
tuple that the bundled baseline guardrail denies but your policy no longer
does. Exit code is 1 whenever any check fails, so it's CI-friendly out of
the box.
Only files with a same-named counterpart in the bundled baseline catalog are
checked. A custom SCP with no LZA-default analog has nothing to prove a
baseline property against yet — invariant scan reports it (to stderr) as
skipped rather than silently ignoring it. Pair it against something of your
own choosing with invariant regression instead.
CLI reference
Both subcommands accept:
| Flag | Default | Meaning |
|---|---|---|
--check IDS |
all | Comma-separated check IDs to run |
--skip-check IDS |
none | Comma-separated check IDs to exclude |
--output, -o |
cli |
cli, json, and/or sarif — repeatable |
--soft-fail |
off | Always exit 0, regardless of findings |
--compact |
off | Suppress PASS lines in cli output |
--out-dir |
solver-results |
Where json/sarif reports get written |
invariant scan also takes:
| Flag | Default | Meaning |
|---|---|---|
--directory, -d |
. |
Root to scan for service-control-policies/ and rcp-policies/ |
invariant regression also takes:
| Flag | Default | Meaning |
|---|---|---|
--manifest, -m |
manifest.json |
Reference↔candidate pairing (see build_manifest.py) |
Suppressing a finding
A candidate JSON file can carry a top-level _suppressions array (raw AWS
policy JSON has no comment syntax to piggyback a suppression on, unlike
HCL/YAML checks):
{
"Version": "2012-10-17",
"Statement": [ ... ],
"_suppressions": [
{"check_id": "INV_AWS_001", "reason": "JIRA-1234: intentional widen"}
]
}
This key is stripped before the policy is handed to the solver, so it never affects the request semantics Z3 reasons about.
Checks
| ID | Severity | What it proves |
|---|---|---|
INV_AWS_001 |
CRITICAL | The candidate SCP/RCP denies every (action, resource, principal) request its reference (baseline or paired manifest entry) explicitly denied. |
Failing INV_AWS_001: review the counterexample. If the widened permission
is intentional, add a _suppressions entry (or update your own reference,
for regression) and document why in the PR description. If not, revert the
Condition/Resource/Action change that removed the denial.
How the solver works, and where it's intentionally conservative
Every Deny statement becomes a Z3 predicate over a symbolic request
context (one variable per condition key actually observed in the LZA
guardrail set); wildcard Action/Resource/ARN patterns compile to Z3
regular expressions. invariant then asks the solver to find a request the
reference denies that the candidate doesn't. See policy_check.py for the
full model. A few gaps are called out there and worth knowing about:
aws:CalledViais modeled as a single representative string, not a true call chain, soForAllValues:StringNotEqualson it is sound for catching "the whole chain is now unconstrained" but can miss a regression that only appears with one specific multi-hop chain.aws:ResourceTag/*(StringEquals) is left unconstrained — there's no resource-tag model — which makes the checker conservative (won't false-negative a real regression) but can over-report on tag-gated statements.- Any condition operator not in the model (currently seen in the wild:
StringLike,StringNotLike,ForAnyValue:StringLikeon keys likerds:DatabaseEngine) is treated as non-constraining and prints aWARNING. A statement using one of these may be reported as denying more than it actually does — verify those findings by hand.
None of this changes the exit-code contract; it just means a PASS on a
statement using one of the flagged operators is a weaker guarantee than a
PASS elsewhere, and the tool tells you when that's the case.
CI integration
.gitlab-ci.yml and .github/workflows/invariant.yml are templates,
meant to be copied into your own AWS LZA config repo (not into a fork of
this one) — each wires up a diff-scoped regression job plus a full-sweep
scan job, and publishes solver-results*/results.sarif so GitLab/GitHub
render findings natively. Both assume invariant is installed from wherever
you publish it — set INVARIANT_PIP_SPEC (GitLab) / INVARIANT_PIP_SPEC env
(GitHub) to your private index, package registry, or a git+https:// URL.
Development
git clone https://github.com/YOUR_ORG/invariant.git
cd invariant
python -m venv .venv
. .venv/Scripts/activate # .venv/bin/activate on macOS/Linux
pip install -e .
invariant --help
License
Apache-2.0 — see LICENSE.
Metadata
Release files for invariant-sast 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| invariant_sast-0.1.0.tar.gz | 24.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| invariant_sast-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.9 kB
Release files / invariant_sast-0.1.0.tar.gz
| Download URL | invariant_sast-0.1.0.tar.gz |
|---|---|
| Size | 24.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
401841a00672274a2bb19ef5d3bdf2c480d6ae9139e05bb69598fd615a9c03a7
|
|
BLAKE2b-256 checksum How to use checksums |
c7b11d34c429eaab69954c3779d8f866e1ed86d64091d3dce7614109da2e7149
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency logRelease files / invariant_sast-0.1.0-py3-none-any.whl
| Download URL | invariant_sast-0.1.0-py3-none-any.whl |
|---|---|
| Size | 29.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c6f6ae19eb4c282de0540d0ca6e0b899f163199ffc0207c9876d3e37a8d2ea07
|
|
BLAKE2b-256 checksum How to use checksums |
cbaff516df4bcb7e45adcb4c1ea2e9a7f1f2234b88384dc44019d448d5193d9b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency logSecurity for invariant-sast 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for invariant_sast-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.