
Обнаруживает и автоматически исправляет жестко закодированные секреты в Python-репозиториях — отказывается, когда исправление может сломать ваш код.
Autonoma обнаруживает жёстко зашитые секреты и заменяет их ссылками на os.environ[...] с помощью перезаписи AST. Изменения применяются только тогда, когда перезапись является детерминированной и сохраняет семантику.
Перезаписи используют разобранное синтаксическое дерево, а не регулярные выражения по сырому тексту.
Работает локально, без сетевых вызовов. Работает в 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-строка — отказано, потому что перезапись изменит семантику
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-строка | 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 перед изменением.