Autonoma 检测硬编码的密钥,并使用 AST 重写将其替换为 os.environ[...] 引用。 仅当重写是确定性的且保持语义时,才会应用更改。
重写使用解析后的语法树——而不是基于原始文本的正则表达式。
本地运行,无网络调用。可在 CI 中使用,具有幂等性,差异最小。

代码库中的硬编码密钥:
大多数工具可以检测到它们。
Autonoma 只处理它可以证明安全更改的部分。
autonoma scan .
autonoma fix .
git diff
pip install autonoma-cli
将以下内容添加到您的 .pre-commit-config.yaml 中,以防止密钥进入历史记录:
- repo: local
hooks:
- id: autonoma
name: Autonoma Scan
entry: autonoma scan
language: system
types: [python]
# 1. 创建一个包含硬编码密钥的测试文件
cat > test_secrets.py << 'EOF'
SENDGRID_API_KEY = "sg-live-abc123xyz789"
DB_PASSWORD = "Pr0dAccess2024!"
EOF
# 同时创建环境变量契约文件(安全修复所需)
printf 'SENDGRID_API_KEY=\nDB_PASSWORD=\n' > .env.example
# 2. 扫描——将 JSON 结果输出到 stdout
autonoma scan test_secrets.py
# 3. 修复——原地重写文件
autonoma fix test_secrets.py
# 4. 再次扫描——现在应为干净状态(退出码 0)
autonoma scan test_secrets.py
# 5. 查看结果
cat test_secrets.py
修复后的预期输出:
import os
SENDGRID_API_KEY = os.environ["SENDGRID_API_KEY"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
检测模式。将 JSON 输出到 stdout,人类可读摘要输出到 stderr。非变更操作——从不修改文件。
# 扫描目录(JSON 结果输出到 stdout)
autonoma scan src/
# 将 JSON 结果保存到文件
autonoma scan src/ > findings.json
scan 的退出码:
| 状态码 | 含义 |
|---|---|
0 | 未发现 |
1 | 发现结果 |
3 | 工具错误 |
使用 AST 重写修复硬编码密钥。原地修改文件。
# 应用修复
autonoma fix src/
# 写入前预览补丁
autonoma fix src/ --diff
# 写入修复审计日志
autonoma fix src/ --report-out audit.json
fix 的退出码:
| 状态码 | 含义 |
|---|---|
0 | 未发现——仓库已干净 |
1 | 修复前存在发现(修复可能已成功——请检查输出中的 FIXED/REFUSED 计数) |
3 | 工具错误 |
fix命令在尝试修复前只要找到密钥,就退出码为1,无论重写是否成功。这是有意为之:CI 流水线应标记引入密钥的提交,即使自动修复成功。之后运行autonoma scan确认仓库已干净。
扫描 git 历史中曾经提交但随后被删除或更改的密钥。
[!NOTE] 仅检测。 此命令不会重写 git 历史或修改提交。
autonoma history-scan .
以下是 Autonoma 当前实际可修复的模式。
# settings.py
SENDGRID_API_KEY = "sg-live-abc123xyz789"
DB_PASSWORD = "Pr0dAccess2024!"
autonoma fix .)# settings.py
import os
SENDGRID_API_KEY = os.environ["SENDGRID_API_KEY"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
# f-string —— 拒绝,因为重写会改变语义
api_key = f"prefix_{BASE_KEY}"
# → REFUSED: refuse_fstring_mixed_expression
# 字典/嵌套值 —— 拒绝,因为目标不是简单赋值
DATABASES = {
"default": {"PASSWORD": "Pr0d@ccess2024!"}
}
# → REFUSED: unsupported assignment target type
被拒绝的发现会在 JSON 输出中报告,并在 CI 中导致非零退出码。含有被拒绝发现的文件不会被修改。
| 模式 | 示例 | 行为 | 原因 |
|---|---|---|---|
| 简单赋值 | api_key = "sk-abc123" | 修复 | 确定性 AST 重写 |
| 类属性 | class C: SECRET = "abc" | 修复 | 确定性 AST 重写 |
| 关键字参数 | connect(password="abc") | 修复 | 确定性 AST 重写 |
| f-string | key = f"prefix_{v}" | 拒绝 | 重写会改变运行时行为 |
| 拼接 | key = "sk-" + suffix | 拒绝 | 重写会改变运行时行为 |
| 字典/嵌套值 | cfg = {"pass": "abc"} | 拒绝 | 不是简单赋值目标 |
| 多重赋值 | A = B = "secret" | 拒绝 | 目标不明确 |
| 已安全 | key = os.getenv("KEY") | 跳过 | 无需更改 |
缺少 .env.example | 任意模式 | 拒绝 | 没有用于推导变量名的环境契约 |
对干净文件重复运行不会做任何更改。重写保留原始缩进和注释。只有缺少 import os 时才会添加。
如果检测到任何密钥,则使构建失败:
- name: Scan for secrets
run: autonoma scan .
analyze 保留用于向后兼容。请迁移至 scan 或 fix。
# 等同于 'autonoma scan'
autonoma analyze src/ --detect-only
# 等同于 'autonoma fix'
autonoma analyze src/ --auto-fix
API_KEY = "secret"class Config: PASS = "secret"connect(password="secret")A = B = "secret")或元组解包。.env.example 或环境契约。被拒绝的情况会在 JSON 输出中报告,并在 CI 中导致非零退出码。如果文件中任何问题被拒绝,该文件永远不会被修改。
<file>.bak。SEC002 目前将凭证值模式与关键字条件路由逻辑相结合。分配给非常不寻常变量名的凭证可能无法被检测到。
Autonoma 有意避免仅基于熵的广泛秘密检测,除非能在标记的假阳性语料库上衡量精度影响。
autonoma scan 输出 detect-only 报告到 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..."
}
]
}
MIT 许可证