AST-based detection and safe remediation of hardcoded secrets in Python and config files
Project description
Autonoma
Autonoma detects hardcoded secrets and replaces them with os.environ[...] references using AST rewrites. Changes are applied only when the rewrite is deterministic and semantically-preserving.
- AST-Based: Rewrites use the parsed syntax tree, not pattern matching on raw text.
- Local & Private: No network calls or external dependencies.
- CI/CD Ready: Idempotent, minimal diffs, and zero-noise operation.
What problem this solves
Hardcoded secrets in codebases:
- secrets get committed and stay in git history
- fixing them manually breaks code or misses edge cases
- teams detect leaks but avoid auto-fix tools because they are unsafe
Most tools detect them.
Autonoma fixes them only when the rewrite is deterministic and semantically-preserving.
Quick example
autonoma scan .
autonoma fix .
git diff
Installation
pip install autonoma-cli
Pre-commit Integration
Add this to your .pre-commit-config.yaml to prevent secrets from entering your history:
- repo: local
hooks:
- id: autonoma
name: Autonoma Scan
entry: autonoma scan
language: system
types: [python]
Try it in 60 seconds
# 1. Create a test file with hardcoded secrets
cat > test_secrets.py << 'EOF'
SENDGRID_API_KEY = "sg-live-abc123xyz789"
DB_PASSWORD = "Pr0dAccess2024!"
EOF
# Also create the env contract file (required for safe remediation)
printf 'SENDGRID_API_KEY=\nDB_PASSWORD=\n' > .env.example
# 2. Scan — emits JSON findings to stdout
autonoma scan test_secrets.py
# 3. Fix — rewrites the file in place
autonoma fix test_secrets.py
# 4. Scan again — should now be clean (exit 0)
autonoma scan test_secrets.py
# 5. Inspect the result
cat test_secrets.py
Expected output after fix:
import os
SENDGRID_API_KEY = os.environ["SENDGRID_API_KEY"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
Commands
scan
Detection mode. Outputs JSON to stdout and a human-readable summary to stderr. Non-mutating — never modifies files.
# Scan a directory (JSON findings to stdout)
autonoma scan src/
# Save JSON results to a file
autonoma scan src/ > findings.json
Exit codes for scan:
| Code | Meaning |
|---|---|
0 |
No findings |
1 |
Findings detected |
3 |
Tool error |
fix
Remediates hardcoded secrets using AST rewrites. Mutates files in place.
# Apply fixes
autonoma fix src/
# Preview patches before writing
autonoma fix src/ --diff
# Write remediation audit log
autonoma fix src/ --report-out audit.json
Exit codes for fix:
| Code | Meaning |
|---|---|
0 |
No findings — repo was already clean |
1 |
Findings existed before remediation (remediation may have succeeded — check output for FIXED/REFUSED counts) |
3 |
Tool error |
The
fixcommand exits1whenever it found secrets before attempting remediation, regardless of whether the rewrite succeeded. This is intentional: CI pipelines should flag the commit where secrets were introduced, even after auto-fix. Runautonoma scanafterward to confirm the repo is clean.
history-scan
Analyzes git history for secrets that were added and subsequently removed or modified.
[!NOTE] Detection only. This command does not rewrite git history or modify commits.
autonoma history-scan .
Before / After
These are the patterns Autonoma actually fixes today.
Before
# settings.py
SENDGRID_API_KEY = "sg-live-abc123xyz789"
DB_PASSWORD = "Pr0dAccess2024!"
After (autonoma fix .)
# settings.py
import os
SENDGRID_API_KEY = os.environ["SENDGRID_API_KEY"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
Refused (refusal-first safety)
# f-string — refused because the rewrite would change semantics
api_key = f"prefix_{BASE_KEY}"
# → REFUSED: refuse_fstring_mixed_expression
# Dict/nested value — refused because the target is not a simple assignment
DATABASES = {
"default": {"PASSWORD": "Pr0d@ccess2024!"}
}
# → REFUSED: unsupported assignment target type
Refused findings are reported in the JSON output and cause a non-zero exit in CI. Files with refused findings are never modified.
What Autonoma fixes vs refuses
| Pattern | Example | Behavior | Why |
|---|---|---|---|
| Simple assignment | api_key = "sk-abc123" |
Fixed | Deterministic AST rewrite |
| Class attribute | class C: SECRET = "abc" |
Fixed | Deterministic AST rewrite |
| Keyword argument | connect(password="abc") |
Fixed | Deterministic AST rewrite |
| f-string | key = f"prefix_{v}" |
Refused | Rewrite would change runtime behavior |
| Concatenation | key = "sk-" + suffix |
Refused | Rewrite would change runtime behavior |
| Dict/nested value | cfg = {"pass": "abc"} |
Refused | Not a simple assignment target |
| Multiple assignment | A = B = "secret" |
Refused | Ambiguous target |
| Already safe | key = os.getenv("KEY") |
Skipped | No change needed |
Missing .env.example |
any pattern | Refused | No env contract to derive variable name |
CI/CD Features
- Idempotent: Re-running on an already-fixed file makes no changes.
- Format-preserving: Rewrites keep original indentation and surrounding comments intact.
- Import-aware: Adds
import osonly when absent; avoids duplicate imports.
Integration & CI/CD
GitHub Actions (Scan Only)
To fail your build if any secrets are detected:
- name: Scan for secrets
run: autonoma scan .
Legacy Commands
analyze is retained for backwards compatibility. Migrate to scan or fix.
# Equivalent to 'autonoma scan'
autonoma analyze src/ --detect-only
# Equivalent to 'autonoma fix'
autonoma analyze src/ --auto-fix
Constraints & Behaviors
What it remediates
- Simple assignments:
API_KEY = "secret" - Class attributes:
class Config: PASS = "secret" - Keyword arguments:
connect(password="secret")
What it refuses (by design)
- Complex Expressions: f-strings, concatenations, or function calls on the RHS.
- Ambiguous Targets: Multiple assignments (
A = B = "secret") or tuple unpacking. - Nested/Dict Values: Values inside dicts, lists, or tuples.
- Missing Context: If no
.env.exampleor environment contract is found in the repo.
Refused cases are reported in JSON output and will cause non-zero exit codes in CI. The file is never modified if any issue in it is refused.
What it does not do
- It does not use entropy/guessing (it uses heuristic name matching).
- It does not modify non-Python files in the Community Edition.
- It does not delete your code; backups are written as
<file>.bakbefore modification.
JSON Schema
autonoma scan outputs a detect-only report to stdout:
{
"schema_version": "1.0",
"tool_name": "autonoma",
"tool_version": "0.1.5",
"generated_at": "2026-03-24T12:00:00Z",
"mode": "detect-only",
"summary": {
"files_processed": 3,
"total_findings": 2,
"safe_to_fix": 1,
"refused": 1
},
"findings": [
{
"file": "settings.py",
"line": 4,
"pattern_type": "api_key",
"severity": "high",
"rule_id": "SEC002",
"safe_to_fix": true,
"suggested_env_var": "SENDGRID_API_KEY",
"refusal_reason": null,
"fingerprint": "sha256:abc123..."
}
]
}
License
MIT 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 autonoma_cli-0.1.7.tar.gz.
File metadata
- Download URL: autonoma_cli-0.1.7.tar.gz
- Upload date:
- Size: 99.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06ad9a626378599f914d06b577367c2876af7fd34922ef9b3e4709543f477528
|
|
| MD5 |
a886a6d5f1aaaff0e9ef0c08a57f3a69
|
|
| BLAKE2b-256 |
d9a3397f59e4e1918d7a11f35448ddd872542d5543d411d4768fea33de5f45dc
|
Provenance
The following attestation bundles were made for autonoma_cli-0.1.7.tar.gz:
Publisher:
publish.yml on VihaanInnovations/autonoma
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
autonoma_cli-0.1.7.tar.gz -
Subject digest:
06ad9a626378599f914d06b577367c2876af7fd34922ef9b3e4709543f477528 - Sigstore transparency entry: 1418276435
- Sigstore integration time:
-
Permalink:
VihaanInnovations/autonoma@9fc5d3a782d98f949649fb3c673e17b23aa1f937 -
Branch / Tag:
refs/tags/v0.1.7 - Owner: https://github.com/VihaanInnovations
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9fc5d3a782d98f949649fb3c673e17b23aa1f937 -
Trigger Event:
push
-
Statement type:
File details
Details for the file autonoma_cli-0.1.7-py3-none-any.whl.
File metadata
- Download URL: autonoma_cli-0.1.7-py3-none-any.whl
- Upload date:
- Size: 49.5 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 |
077e3a174637a37956b87283dc6203e55e32e99202f9226005326e90783a2153
|
|
| MD5 |
7c2981135787b6f96acf074df8bd4c32
|
|
| BLAKE2b-256 |
f04c78c3544d2be0660141c3e4b8492ede27c34ad7a05baa6b29bed3b46a482a
|
Provenance
The following attestation bundles were made for autonoma_cli-0.1.7-py3-none-any.whl:
Publisher:
publish.yml on VihaanInnovations/autonoma
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
autonoma_cli-0.1.7-py3-none-any.whl -
Subject digest:
077e3a174637a37956b87283dc6203e55e32e99202f9226005326e90783a2153 - Sigstore transparency entry: 1418276514
- Sigstore integration time:
-
Permalink:
VihaanInnovations/autonoma@9fc5d3a782d98f949649fb3c673e17b23aa1f937 -
Branch / Tag:
refs/tags/v0.1.7 - Owner: https://github.com/VihaanInnovations
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9fc5d3a782d98f949649fb3c673e17b23aa1f937 -
Trigger Event:
push
-
Statement type: