User guide
End-user documentation from README.md (from AWS SCP Limits through Configuration).
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.
```yaml apiVersion: scpz.io/v1alpha1 kind: OptimizerConfig metadata: name: default spec: optimizer: actionCompress: mode: conservative # conservative | aggressive redundancyEliminate: enabled: true # opt-in