Intelligently optimize AWS SCP JSONs to adhere to strict size, statement, and policy count limits.
Project description
scpz
Intelligently optimize AWS Service Control Policy (SCP) JSONs to fit within AWS's strict limits.
AWS SCP Limits
| Constraint | Limit |
|---|---|
| Policy size | 10,240 bytes |
| Statements per SCP | 5 |
| SCPs per target (account/OU) | 10 |
Requirements
Python 3.13 or later.
Installation
pip install scpz
Or with uv (recommended):
uv tool install scpz
Container images
Images are built from this repository’s Dockerfile (compatible with Docker and Podman) and published when a GitHub Release is published.
Tagging (both registries): each release tag (for example v0.3.0) is always published; latest is updated only for stable releases (not GitHub pre-releases).
ghcr.io/tsjnsn/scpz:<release-tag>andghcr.io/tsjnsn/scpz:latest(GHCR)tsjnsn/scpz:<release-tag>andtsjnsn/scpz:latest(Docker Hub)
GitHub Container Registry
Published to GitHub Container Registry as ghcr.io/tsjnsn/scpz.
Example (optimize a policy file in the current directory):
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" ghcr.io/tsjnsn/scpz:latest optimize policy.json
With Podman:
podman run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work:z" ghcr.io/tsjnsn/scpz:latest optimize policy.json
Docker Hub
Published to Docker Hub as tsjnsn/scpz.
# Pin to a release (recommended in production)
docker pull tsjnsn/scpz:v0.3.0
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" tsjnsn/scpz:v0.3.0 optimize policy.json --dry-run
Build locally
docker build -t scpz:local .
docker run --rm scpz:local --version
Maintainer credentials
The Publish release workflow pushes to GHCR (via GITHUB_TOKEN) and Docker Hub (via repository secrets). Container jobs run only after PyPI publish succeeds so a failed package release does not leave tagged images for an incomplete release. If DOCKERHUB_USERNAME / DOCKERHUB_TOKEN are unset, the Docker Hub job skips with a workflow notice instead of failing the release.
| Secret | Purpose |
|---|---|
DOCKERHUB_USERNAME |
Docker Hub account with push access to tsjnsn/scpz (must be tsjnsn today; the workflow image name is fixed, not derived from this secret) |
DOCKERHUB_TOKEN |
Docker Hub access token with Read & Write scope for that account |
Ensure the Docker Hub repository tsjnsn/scpz exists before the first push.
Usage
Optimize
# Optimize a single file in-place (original saved as policy.json.bak)
scpz optimize policy.json
# Optimize all JSON files in a directory
scpz optimize policies/
# Dry run — show diff + summary without writing
scpz optimize policy.json --dry-run
# Summary only — just show what would change
scpz optimize policy.json --summary-only
# Write to a different file
scpz optimize policy.json --output optimized.json
# Error instead of auto-splitting
scpz optimize policy.json --no-split
Validate
# Validate a single file
scpz validate policy.json
# Validate all JSON files in a directory
scpz validate policies/
Check equivalence
# Ensure optimized output did not broaden permissions (catalog model)
scpz check-equivalence policy.json policy.optimized.json
Optimization Passes
scpz runs the following optimizations in order, repeating until the output stops changing (up to 5 rounds):
- Statement merging — Combines statements that share the same Effect, Condition, and Resource into a single statement with a unioned Action list.
- Action wildcard compression — Replaces groups of actions sharing a common prefix with wildcard patterns (e.g.
s3:GetObject+s3:GetBucketPolicy→s3:Get*). Uses the bundled AWS action catalog in conservative mode to avoid scope broadening. - Condition merging — Deduplicates condition values and merges equivalent condition blocks.
- Resource ARN optimization — Collapses multiple specific ARNs into wildcard patterns (e.g.
role/Admin+role/ReadOnly→role/*). - Redundancy elimination (opt-in) — Removes statements wholly subsumed by another statement in the same policy (
Actionvia wildcards;NotActiononly when a non-empty action catalog is configured, mirroring catalog safety forNotActioncompression). Enable withredundancyEliminate.enabled: trueinscpz.yaml.
When a policy still exceeds limits after optimization, scpz automatically splits it into multiple SCP documents (up to 10 per target).
Configuration
Place a scpz.yaml in your project root (scpz walks up from the input file to find it). See examples/scpz.yaml for a fully-annotated reference.
apiVersion: scpz.io/v1alpha1
kind: OptimizerConfig
metadata:
name: default
spec:
optimizer:
actionCompress:
mode: conservative # conservative | aggressive
redundancyEliminate:
enabled: true # opt-in
# Print the JSON Schema for editor validation
scpz print-schema
Development
# Install with dev dependencies
uv sync --dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=scpz
# Run a specific test file
uv run pytest tests/test_actions.py -v
# Regenerate the committed schema after model changes
uv run scpz print-schema -o schema/OptimizerConfig.json
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scpz-0.3.1.tar.gz.
File metadata
- Download URL: scpz-0.3.1.tar.gz
- Upload date:
- Size: 245.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76eb25cbd7db4850c81e4af3381e39d4e291094ee1c8c0fe46299b08b08ffe52
|
|
| MD5 |
9f2d140adb414125bbf0fec710622110
|
|
| BLAKE2b-256 |
dbdc66b5ea20ee073e413333abb77987b538630381e0de8d78e59f6657190e87
|
Provenance
The following attestation bundles were made for scpz-0.3.1.tar.gz:
Publisher:
publish.yml on tsjnsn/scpz
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scpz-0.3.1.tar.gz -
Subject digest:
76eb25cbd7db4850c81e4af3381e39d4e291094ee1c8c0fe46299b08b08ffe52 - Sigstore transparency entry: 1616342714
- Sigstore integration time:
-
Permalink:
tsjnsn/scpz@e54e17e8323dd357bbdd05a14b97de27476f80ae -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/tsjnsn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e54e17e8323dd357bbdd05a14b97de27476f80ae -
Trigger Event:
release
-
Statement type:
File details
Details for the file scpz-0.3.1-py3-none-any.whl.
File metadata
- Download URL: scpz-0.3.1-py3-none-any.whl
- Upload date:
- Size: 150.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b506b0539212bd45183228462024c49f3843c3d169b333ceca71c160d4cfcfe7
|
|
| MD5 |
8eccb5a0a1734d0440e687f6d1790692
|
|
| BLAKE2b-256 |
048853bdd589614767c888cc16eb53c30f34b72a37cb9de5ea1dfc939404d233
|
Provenance
The following attestation bundles were made for scpz-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on tsjnsn/scpz
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scpz-0.3.1-py3-none-any.whl -
Subject digest:
b506b0539212bd45183228462024c49f3843c3d169b333ceca71c160d4cfcfe7 - Sigstore transparency entry: 1616342724
- Sigstore integration time:
-
Permalink:
tsjnsn/scpz@e54e17e8323dd357bbdd05a14b97de27476f80ae -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/tsjnsn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e54e17e8323dd357bbdd05a14b97de27476f80ae -
Trigger Event:
release
-
Statement type: