IaC misconfiguration scanner — score, track, and benchmark cloud security posture.
Project description
Misconfig Index
IaC misconfiguration scanner — score, track, and benchmark cloud security posture.
Misconfig Index scans Terraform, Kubernetes, CloudFormation, and Dockerfile IaC, applies rule packs, and converts findings into a single weighted Misconfig Score (0–100). Scores are tracked over time so you can see your security posture improving — or catch regressions before they reach production.
Features
- Instant score — one command to scan any IaC directory and get a weighted grade (A–F)
- Category breakdown — per-domain scores: networking, identity, storage, workload, image
- Trend tracking — every scan is stored; see your score history over time
- CI gate — fail a build if score drops below your threshold
- Live badges — embed your current score in any README
- REST API — ingest scans from any tool, query history, benchmark against the field
- Self-hostable — one
docker compose upto run the full stack with PostgreSQL
Quick start
Install
pip install misconfig-index
Scan a directory
misconfig scan --path ./infra
Scanning /home/user/infra …
────────────────────────────────────────
Misconfig Score: 76/100 (Grade B)
────────────────────────────────────────
Category breakdown:
networking ████████░░ 80/100
identity ███████░░░ 70/100
storage █████████░ 90/100
workload ███████░░░ 72/100
────────────────────────────────────────
Get JSON output (for scripting)
misconfig scan --path ./infra --output json | jq '.score'
CI / GitHub Actions
Gate every pull request on your Misconfig Score in three steps:
1. Add MISCONFIG_API_KEY to your repository secrets.
2. Drop this workflow into .github/workflows/misconfig-index.yml:
name: Misconfig Index
on:
push:
paths: ['**.tf', '**.yaml', '**.yml', '**/Dockerfile']
pull_request:
paths: ['**.tf', '**.yaml', '**.yml', '**/Dockerfile']
env:
MIN_SCORE: 60
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install misconfig-index
- name: Scan IaC
env:
MISCONFIG_API_KEY: ${{ secrets.MISCONFIG_API_KEY }}
run: |
misconfig ingest \
--path . \
--repo "${{ github.repository }}" \
--branch "${{ github.ref_name }}" \
--commit "${{ github.sha }}" \
--min-score $MIN_SCORE
3. Adjust MIN_SCORE to your quality gate.
The scanner exits 0 on success, 1 if below threshold, 2 on error — GitHub will fail the check automatically.
Score badges
Add a live score badge to your README that updates on every push:

Badges are grade-coloured (🟢 A, 🟡 C, 🔴 F) and cached for 5 minutes.
CLI reference
Usage: misconfig [OPTIONS] COMMAND [ARGS]...
Misconfig Index — IaC misconfiguration scanner & API.
Commands:
scan Scan IaC files and print the misconfiguration score.
ingest Scan and push results to the Misconfig Index API.
serve Start the Misconfig Index API server.
misconfig scan
| Option | Default | Description |
|---|---|---|
--path, -p |
. |
Directory to scan |
--output, -o |
table |
table or json |
--save |
off | Persist to local DB (requires DATABASE_URL) |
misconfig ingest
| Option | Env var | Description |
|---|---|---|
--path, -p |
— | Directory to scan |
--repo, -r |
— | Repo identifier (e.g. github.com/org/repo) |
--api-key |
MISCONFIG_API_KEY |
API key |
--api-url |
MISCONFIG_API_URL |
API base URL |
--branch |
MISCONFIG_BRANCH |
Git branch |
--commit |
MISCONFIG_COMMIT |
Git commit SHA |
--min-score |
— | Fail if score is below this value |
--dry-run |
off | Print payload without posting |
misconfig serve
| Option | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Bind host |
--port |
8000 |
Bind port |
--workers |
1 |
Worker processes |
--reload |
off | Auto-reload (dev) |
REST API
Interactive docs are available at /docs when the server is running.
Create an organisation
curl -X POST https://api.misconfig.dev/v1/orgs \
-H "Content-Type: application/json" \
-d '{"name": "Acme", "slug": "acme"}'
Create an API key
curl -X POST https://api.misconfig.dev/v1/orgs/{id}/keys \
-H "Content-Type: application/json" \
-d '{"name": "ci-prod"}'
# Returns the full key (mi_…) once — save it to your secrets manager now.
Ingest a scan
curl -X POST https://api.misconfig.dev/v1/ingest \
-H "X-API-Key: mi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"repo": "github.com/acme/infra",
"branch": "main",
"commit_sha": "abc123",
"total_files_scanned": 42,
"findings": [
{
"rule_id": "TF_OPEN_SG_0_0_0_0",
"file_path": "network/sg.tf",
"file_type": "terraform",
"line_start": 14,
"snippet": "cidr_blocks = [\"0.0.0.0/0\"]"
}
]
}'
Get repo score history
curl https://api.misconfig.dev/v1/repos/{id}/history \
-H "X-API-Key: mi_YOUR_KEY"
Self-hosting
# 1. Set your database password (required)
cp .env.example .env
# edit .env → set POSTGRES_PASSWORD
# 2. Start the full stack
docker compose up -d
# 3. Open the dashboard
open http://localhost
| Service | Image | Role |
|---|---|---|
db |
postgres:16-alpine |
Persistent data store |
api |
Built from Dockerfile |
FastAPI backend (auto-creates schema on first boot) |
web |
nginx:1.27-alpine |
Frontend static files + /api reverse proxy |
Environment variables
| Variable | Default | Description |
|---|---|---|
POSTGRES_PASSWORD |
(required) | PostgreSQL password |
DATABASE_URL |
sqlite:///./misconfig_index.db |
Override for SQLite dev |
ENVIRONMENT |
development |
development or production |
API_WORKERS |
2 |
uvicorn worker count |
CORS_ORIGINS |
* |
Allowed origins (set to your domain in prod) |
WEB_PORT |
80 |
Host port for nginx |
Scoring
Findings are weighted by severity, normalised by files scanned:
| Severity | Weight |
|---|---|
| Critical | 10 |
| High | 5 |
| Medium | 2 |
| Low | 1 |
score = clamp(0, 100 − (Σ weights / files_scanned) × 10)
| Grade | Score range |
|---|---|
| A | ≥ 90 |
| B | ≥ 75 |
| C | ≥ 60 |
| D | ≥ 40 |
| F | < 40 |
Supported IaC
| Type | Detected by | Rule categories |
|---|---|---|
| Terraform | .tf extension |
networking, identity, storage, database |
| Kubernetes | .yaml/.yml |
workload, storage, image |
| CloudFormation | AWSTemplateFormatVersion in YAML |
networking, storage |
| Dockerfile | filename Dockerfile |
image |
Development
git clone https://github.com/misconfig-index/misconfig-index
cd misconfig-index
python -m venv .venv && source .venv/bin/activate
pip install -e .
# Scan the included sample fixtures
misconfig scan --path samples/
# Start the API with hot-reload
misconfig serve --reload
# Run with the Python module path as well
python -m scanner scan --path samples/
Contributing
Contributions are welcome. The highest-impact areas right now:
- New rules — add a
.pytoscanner/rules/following theRulebase class pattern - Rule improvements — reduce false positives in existing regex patterns
- New IaC types — Pulumi, Ansible, Bicep, ARM templates
Please open an issue before submitting large PRs.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
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 misconfig_index-0.2.0.tar.gz.
File metadata
- Download URL: misconfig_index-0.2.0.tar.gz
- Upload date:
- Size: 42.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
738f2e8f8280f2f5449dc53a58c1de58a7a1dcd22700c25501fda2f71c14dd67
|
|
| MD5 |
31c32280fcd3c83be7d0fc0ddab83150
|
|
| BLAKE2b-256 |
43d9c656622d1982e890337b09df50a6369786a411b57b6c985ffff0ae774130
|
Provenance
The following attestation bundles were made for misconfig_index-0.2.0.tar.gz:
Publisher:
publish.yml on cjb00/misconfig-index
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
misconfig_index-0.2.0.tar.gz -
Subject digest:
738f2e8f8280f2f5449dc53a58c1de58a7a1dcd22700c25501fda2f71c14dd67 - Sigstore transparency entry: 1005767084
- Sigstore integration time:
-
Permalink:
cjb00/misconfig-index@6a3f5f21ac410fa6a0709321fd8c3ada7bc07739 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/cjb00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a3f5f21ac410fa6a0709321fd8c3ada7bc07739 -
Trigger Event:
push
-
Statement type:
File details
Details for the file misconfig_index-0.2.0-py3-none-any.whl.
File metadata
- Download URL: misconfig_index-0.2.0-py3-none-any.whl
- Upload date:
- Size: 49.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38f3e6498ce3651c6728842d86d6de490da40cf103615170f811d52c36b13146
|
|
| MD5 |
d4e9add1050279935a04063c22cbbd08
|
|
| BLAKE2b-256 |
d79ddca2c179d8622fac457a6e8f5fb5f45cf106f2cf15a16af40bd7f9da91b3
|
Provenance
The following attestation bundles were made for misconfig_index-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on cjb00/misconfig-index
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
misconfig_index-0.2.0-py3-none-any.whl -
Subject digest:
38f3e6498ce3651c6728842d86d6de490da40cf103615170f811d52c36b13146 - Sigstore transparency entry: 1005767098
- Sigstore integration time:
-
Permalink:
cjb00/misconfig-index@6a3f5f21ac410fa6a0709321fd8c3ada7bc07739 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/cjb00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6a3f5f21ac410fa6a0709321fd8c3ada7bc07739 -
Trigger Event:
push
-
Statement type: