aigis-sast 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jasperBLCK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,267 @@
1
+ Metadata-Version: 2.4
2
+ Name: aigis-sast
3
+ Version: 0.2.0
4
+ Summary: AigisSAST: security scanner for AI-generated code. Finds what vibe coding leaves behind and explains how to fix it
5
+ Author: jasperBLCK
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jasperBLCK/AigisSAST
8
+ Project-URL: Source, https://github.com/jasperBLCK/AigisSAST
9
+ Project-URL: Issues, https://github.com/jasperBLCK/AigisSAST/issues
10
+ Project-URL: Changelog, https://github.com/jasperBLCK/AigisSAST/releases
11
+ Keywords: security,sast,linter,secrets,ai,vibe-coding,fastapi
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Security
17
+ Classifier: Topic :: Software Development :: Quality Assurance
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8; extra == "dev"
23
+ Requires-Dist: ruff>=0.6; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ <div align="center">
27
+
28
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/header.svg" width="100%" alt="AigisSAST"/>
29
+
30
+ <h3>ИИ пишет код быстро. AigisSAST проверяет, что он не оставил дыр.</h3>
31
+
32
+ <p><i>Security scanner for AI-generated code: finds what vibe coding leaves behind and explains how to fix it.</i></p>
33
+
34
+ <a href="https://github.com/jasperBLCK/AigisSAST/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/jasperBLCK/AigisSAST/ci.yml?branch=main&style=for-the-badge&label=CI&labelColor=000000&color=333333&logo=githubactions&logoColor=white"/></a> <img src="https://img.shields.io/badge/python-3.9+-000000?style=for-the-badge&logo=python&logoColor=white"/> <img src="https://img.shields.io/badge/dependencies-0-000000?style=for-the-badge"/> <img src="https://img.shields.io/badge/SARIF-2.1.0-000000?style=for-the-badge&logo=github&logoColor=white"/> <img src="https://img.shields.io/badge/aigis-A%20100%2F100-000000?style=for-the-badge"/> <img src="https://img.shields.io/badge/license-MIT-000000?style=for-the-badge"/>
35
+
36
+ </div>
37
+
38
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/divider.svg" width="100%"/>
39
+
40
+ <div align="center">
41
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/demo.svg" width="92%" alt="aigis scan demo"/>
42
+ </div>
43
+
44
+ ## Зачем
45
+
46
+ Cursor, Copilot и ChatGPT собирают рабочий бэкенд за вечер. Но по пути они почти всегда оставляют одно и то же:
47
+ токен бота прямо в коде, `f"SELECT ... {user}"`, `allow_origins=["*"]`, `debug=True`, открытый наружу Postgres.
48
+ Код работает, тесты зелёные, а сервис уже можно взломать.
49
+
50
+ AigisSAST ловит именно эти ошибки и объясняет каждую **простым языком**: чем она опасна и как исправить, с готовым кодом.
51
+ Без регистрации и облака, ничего не отправляет наружу (если сам не включишь `--ai`).
52
+
53
+ - **Ноль зависимостей.** Только стандартная библиотека Python, ставится за секунду.
54
+ - **AST, а не grep.** Python-код разбирается синтаксическим деревом, поэтому меньше ложных срабатываний.
55
+ - **Секреты маскируются** в отчёте: `sk-p************`, сам ключ в логах CI не светится.
56
+ - **Не только ругается, но и чинит.** `aigis fix` сам выносит ключи в `.env` и правит безопасные случаи.
57
+ - **Видит прошлое.** `aigis history` находит ключи, которые «удалили», но они остались в истории git.
58
+ - **CI-ready.** Exit-коды, SARIF для GitHub Code Scanning, JSON, Markdown, GitHub Action и pre-commit hook.
59
+
60
+ ## Быстрый старт
61
+
62
+ ```bash
63
+ pip install git+https://github.com/jasperBLCK/AigisSAST
64
+ aigis scan . # найти проблемы
65
+ aigis fix --dry-run # посмотреть, что исправится автоматически
66
+ aigis fix # исправить
67
+ aigis history # проверить всю историю git на утёкшие ключи
68
+ ```
69
+
70
+ Попробуй на заведомо дырявом примере:
71
+
72
+ ```bash
73
+ git clone https://github.com/jasperBLCK/AigisSAST && cd AigisSAST
74
+ pip install . && aigis scan examples
75
+ ```
76
+
77
+ ## Что ловит
78
+
79
+ | ID | Уровень | Проблема | Где |
80
+ |---|---|---|---|
81
+ | `AIG001` | CRITICAL | Токены и API-ключи: OpenAI, Anthropic, Telegram, GitHub, AWS, Stripe, Slack, Google, Yandex Cloud | везде |
82
+ | `AIG004` | CRITICAL | Приватные ключи (RSA / EC / OpenSSH / PGP) | везде |
83
+ | `AIG002` | HIGH | Захардкоженные пароли и секреты | Python, JS/TS, YAML, Dockerfile, compose, .ini/.toml |
84
+ | `AIG003` | HIGH | Строка подключения к БД с паролем | везде, кроме документации |
85
+ | `AIG005` | HIGH | `.env` попадает в репозиторий | git / .gitignore |
86
+ | `AIG010` | HIGH | SQL-инъекция: f-string, `%`, `+`, `.format()` в `execute()` / `text()` | Python |
87
+ | `AIG011` | HIGH | Shell-инъекция: `shell=True`, `os.system` с переменной | Python |
88
+ | `AIG012` | HIGH | `eval` / `exec` над динамическими данными | Python |
89
+ | `AIG013` | HIGH | `pickle.loads`, `yaml.load` без SafeLoader | Python |
90
+ | `AIG017` | HIGH | JWT без проверки подписи, `algorithms=["none"]` | Python |
91
+ | `AIG030` | HIGH | `eval` / `new Function` | JS / TS |
92
+ | `AIG014` | MEDIUM | `debug=True`, `DEBUG = True` | Python |
93
+ | `AIG015` | MEDIUM → HIGH | CORS `*` (HIGH вместе с `allow_credentials=True`) | FastAPI, Express |
94
+ | `AIG016` | MEDIUM | `verify=False` в requests / httpx | Python |
95
+ | `AIG019` | MEDIUM | Токены и OTP-коды через `random` вместо `secrets` | Python |
96
+ | `AIG031` | MEDIUM | XSS: `innerHTML`, `dangerouslySetInnerHTML`, `v-html` | JS / TS / Vue |
97
+ | `AIG032` | MEDIUM | Секреты в `NEXT_PUBLIC_*` / `VITE_*` / `REACT_APP_*` | фронтенд |
98
+ | `AIG042` | MEDIUM | Порт БД/Redis опубликован на `0.0.0.0` | docker-compose |
99
+ | `AIG018` | LOW | MD5 / SHA-1 | Python |
100
+ | `AIG021` | LOW | POST/PUT/PATCH/DELETE без `Depends(...)` авторизации | FastAPI |
101
+ | `AIG040` | LOW | Контейнер работает от root | Dockerfile |
102
+
103
+ Подробно про любое правило: `aigis explain AIG010`, полный список: `aigis rules`.
104
+
105
+ ## Использование
106
+
107
+ ```bash
108
+ aigis scan . # текстовый отчёт с объяснениями
109
+ aigis scan src api --quiet # несколько путей, без блоков «чем опасно / как исправить»
110
+ aigis scan . --min-severity medium # скрыть LOW
111
+ aigis scan . --fail-on critical # падать только на критичных
112
+ aigis scan . -f sarif -o aigis.sarif # для GitHub Code Scanning
113
+ aigis scan . -f json | jq '.findings[]' # для своих скриптов
114
+ aigis scan . -f markdown > SECURITY.md # отчёт для PR / заказчика
115
+ ```
116
+
117
+ | Exit code | Значение |
118
+ |---|---|
119
+ | `0` | нет находок уровня `--fail-on` (по умолчанию `high`) и выше |
120
+ | `1` | найдены проблемы уровня `--fail-on` и выше |
121
+ | `2` | ошибка запуска (неверный путь, неизвестное правило) |
122
+
123
+ В конце отчёта выводится **security score** (0–100) и оценка от `A` до `F`.
124
+
125
+ ### Исключения
126
+
127
+ ```python
128
+ eval(trusted_expr) # aigis: ignore
129
+ DEBUG = True # aigis: ignore[AIG014]
130
+ ```
131
+
132
+ Файлы и папки исключаются через `.aigisignore` (glob-шаблоны) или флаг `--exclude`.
133
+ `node_modules`, `.venv`, `dist` и lock-файлы пропускаются автоматически, а в git-репозитории ещё и всё из `.gitignore`.
134
+
135
+ ## Автоисправление: `aigis fix`
136
+
137
+ ```bash
138
+ aigis fix --dry-run # только показать diff
139
+ aigis fix # применить
140
+ ```
141
+
142
+ Исправляет только то, что можно поменять без риска сломать логику:
143
+
144
+ | Было | Стало |
145
+ |---|---|
146
+ | `BOT_TOKEN = "7312845567:AA..."` | `BOT_TOKEN = os.environ["BOT_TOKEN"]` + значение в `.env`, ключ в `.env.example` |
147
+ | `connect(password="Pa55word!")` | `connect(password=os.environ["PASSWORD"])` |
148
+ | `.env` не в `.gitignore` | `.env` добавлен в `.gitignore` |
149
+ | `requests.get(url, verify=False)` | `verify=True` |
150
+ | `yaml.load(data)` | `yaml.safe_load(data)` |
151
+ | `otp = random.randint(100000, 999999)` | `otp = secrets.randbelow(900000) + 100000` |
152
+ | `token = random.choice(alphabet)` | `secrets.choice(alphabet)` |
153
+ | `app.run(debug=True)` / `DEBUG = True` | `debug=False` / `DEBUG = os.getenv("DEBUG") == "1"` |
154
+ | `- "5432:5432"` в compose | `- "127.0.0.1:5432:5432"` |
155
+
156
+ Нужные `import os` / `import secrets` добавляются сами. SQL-инъекции, `eval`, `pickle`, JWT и CORS
157
+ автоматически не трогаются: там нужно понимать логику приложения, поэтому они остаются в списке «вручную».
158
+
159
+ ## Утёкшие ключи в истории: `aigis history`
160
+
161
+ ```text
162
+ $ aigis history
163
+ aigis history: секретов в истории git: 1
164
+
165
+ [CRITICAL] AIG001 7312************ (Telegram bot token)
166
+ bot.py · коммит 402c92b от 2026-09-14 · удалён, но остался в истории
167
+ ```
168
+
169
+ Удалить ключ новым коммитом мало: старая версия файла остаётся в истории, и её видит любой, кто склонировал репо.
170
+ `aigis history` проходит по всем коммитам и веткам, показывает, где и когда ключ появился и есть ли он в коде сейчас.
171
+ Ключи в отчёте замаскированы. Для CI: `aigis history -f json`, exit code `1`, если что-то найдено.
172
+
173
+ ## Бейдж для README
174
+
175
+ ```bash
176
+ aigis badge # пишет aigis-badge.svg с оценкой, например «aigis | A 100/100»
177
+ aigis badge --url # ссылка shields.io, без файла
178
+ ```
179
+
180
+ ## AI-режим
181
+
182
+ ```bash
183
+ export AIGIS_API_KEY=... # любой OpenAI-совместимый API
184
+ export AIGIS_BASE_URL=https://api.openai.com/v1 # или свой прокси / локальная LLM
185
+ export AIGIS_MODEL=gpt-4o-mini
186
+ aigis scan . --ai
187
+ ```
188
+
189
+ Для каждой находки модель получает фрагмент кода вокруг неё и возвращает исправление именно под твой код.
190
+ **Находки с секретами (`AIG001`–`AIG005`) в модель никогда не отправляются.** Если ключа нет или API недоступен,
191
+ сканер предупредит об этом и покажет стандартные советы.
192
+
193
+ ## GitHub Actions
194
+
195
+ ```yaml
196
+ name: security
197
+ on: [push, pull_request]
198
+
199
+ jobs:
200
+ aigis:
201
+ runs-on: ubuntu-latest
202
+ permissions:
203
+ contents: read
204
+ security-events: write
205
+ steps:
206
+ - uses: actions/checkout@v4
207
+ - uses: jasperBLCK/AigisSAST@v0.2.0
208
+ with:
209
+ fail-on: high
210
+ ```
211
+
212
+ Находки появятся во вкладке **Security → Code scanning** и прямо в diff пулл-реквеста.
213
+
214
+ ## pre-commit
215
+
216
+ ```yaml
217
+ repos:
218
+ - repo: https://github.com/jasperBLCK/AigisSAST
219
+ rev: v0.2.0
220
+ hooks:
221
+ - id: aigis
222
+ ```
223
+
224
+ ## Как устроено
225
+
226
+ ```text
227
+ aigis scan
228
+ │
229
+ ├── scanner git ls-files → фильтры → .aigisignore → чтение файлов
230
+ ├── checks/
231
+ │ ├── python_ast ast.NodeVisitor: SQL, shell, eval, pickle, JWT, CORS, debug, random, FastAPI auth
232
+ │ └── text regex: токены, ключи, DSN, конфиги, Dockerfile, compose, JS/TS
233
+ ├── ai (опционально) OpenAI-совместимый API, секреты отфильтрованы
234
+ └── report text · json · sarif · markdown + score / grade
235
+
236
+ aigis fix AST-позиции находок → точечные правки → .env / .env.example / .gitignore
237
+ aigis history git log -p --all → добавленные строки → те же детекторы секретов
238
+ aigis badge score / grade → SVG или shields.io
239
+ ```
240
+
241
+ Разработка:
242
+
243
+ ```bash
244
+ python -m venv .venv && . .venv/bin/activate
245
+ pip install -e ".[dev]"
246
+ ruff check . && ruff format --check . && pytest -q
247
+ ```
248
+
249
+ ## Roadmap
250
+
251
+ - [x] Python AST-правила, секреты, конфиги, Dockerfile, compose, JS/TS
252
+ - [x] SARIF, JSON, Markdown, GitHub Action, pre-commit
253
+ - [x] AI-исправления через OpenAI-совместимый API
254
+ - [x] `aigis fix`: автоматическое исправление безопасных случаев (вынос секретов в `.env`)
255
+ - [x] `aigis history`: поиск секретов во всей истории git
256
+ - [x] `aigis badge`: бейдж с оценкой безопасности
257
+ - [ ] Проверка зависимостей на известные CVE и тайпсквоттинг
258
+ - [ ] Правила для Django и Flask, Go и PHP
259
+ - [ ] VS Code extension
260
+
261
+ ## License
262
+
263
+ [MIT](https://github.com/jasperBLCK/AigisSAST/blob/main/LICENSE)
264
+
265
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/divider.svg" width="100%"/>
266
+
267
+ <div align="center"><sub>built by <a href="https://github.com/jasperBLCK">jasperBLCK</a> · ship fast, ship secure</sub></div>
@@ -0,0 +1,242 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/header.svg" width="100%" alt="AigisSAST"/>
4
+
5
+ <h3>ИИ пишет код быстро. AigisSAST проверяет, что он не оставил дыр.</h3>
6
+
7
+ <p><i>Security scanner for AI-generated code: finds what vibe coding leaves behind and explains how to fix it.</i></p>
8
+
9
+ <a href="https://github.com/jasperBLCK/AigisSAST/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/jasperBLCK/AigisSAST/ci.yml?branch=main&style=for-the-badge&label=CI&labelColor=000000&color=333333&logo=githubactions&logoColor=white"/></a> <img src="https://img.shields.io/badge/python-3.9+-000000?style=for-the-badge&logo=python&logoColor=white"/> <img src="https://img.shields.io/badge/dependencies-0-000000?style=for-the-badge"/> <img src="https://img.shields.io/badge/SARIF-2.1.0-000000?style=for-the-badge&logo=github&logoColor=white"/> <img src="https://img.shields.io/badge/aigis-A%20100%2F100-000000?style=for-the-badge"/> <img src="https://img.shields.io/badge/license-MIT-000000?style=for-the-badge"/>
10
+
11
+ </div>
12
+
13
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/divider.svg" width="100%"/>
14
+
15
+ <div align="center">
16
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/demo.svg" width="92%" alt="aigis scan demo"/>
17
+ </div>
18
+
19
+ ## Зачем
20
+
21
+ Cursor, Copilot и ChatGPT собирают рабочий бэкенд за вечер. Но по пути они почти всегда оставляют одно и то же:
22
+ токен бота прямо в коде, `f"SELECT ... {user}"`, `allow_origins=["*"]`, `debug=True`, открытый наружу Postgres.
23
+ Код работает, тесты зелёные, а сервис уже можно взломать.
24
+
25
+ AigisSAST ловит именно эти ошибки и объясняет каждую **простым языком**: чем она опасна и как исправить, с готовым кодом.
26
+ Без регистрации и облака, ничего не отправляет наружу (если сам не включишь `--ai`).
27
+
28
+ - **Ноль зависимостей.** Только стандартная библиотека Python, ставится за секунду.
29
+ - **AST, а не grep.** Python-код разбирается синтаксическим деревом, поэтому меньше ложных срабатываний.
30
+ - **Секреты маскируются** в отчёте: `sk-p************`, сам ключ в логах CI не светится.
31
+ - **Не только ругается, но и чинит.** `aigis fix` сам выносит ключи в `.env` и правит безопасные случаи.
32
+ - **Видит прошлое.** `aigis history` находит ключи, которые «удалили», но они остались в истории git.
33
+ - **CI-ready.** Exit-коды, SARIF для GitHub Code Scanning, JSON, Markdown, GitHub Action и pre-commit hook.
34
+
35
+ ## Быстрый старт
36
+
37
+ ```bash
38
+ pip install git+https://github.com/jasperBLCK/AigisSAST
39
+ aigis scan . # найти проблемы
40
+ aigis fix --dry-run # посмотреть, что исправится автоматически
41
+ aigis fix # исправить
42
+ aigis history # проверить всю историю git на утёкшие ключи
43
+ ```
44
+
45
+ Попробуй на заведомо дырявом примере:
46
+
47
+ ```bash
48
+ git clone https://github.com/jasperBLCK/AigisSAST && cd AigisSAST
49
+ pip install . && aigis scan examples
50
+ ```
51
+
52
+ ## Что ловит
53
+
54
+ | ID | Уровень | Проблема | Где |
55
+ |---|---|---|---|
56
+ | `AIG001` | CRITICAL | Токены и API-ключи: OpenAI, Anthropic, Telegram, GitHub, AWS, Stripe, Slack, Google, Yandex Cloud | везде |
57
+ | `AIG004` | CRITICAL | Приватные ключи (RSA / EC / OpenSSH / PGP) | везде |
58
+ | `AIG002` | HIGH | Захардкоженные пароли и секреты | Python, JS/TS, YAML, Dockerfile, compose, .ini/.toml |
59
+ | `AIG003` | HIGH | Строка подключения к БД с паролем | везде, кроме документации |
60
+ | `AIG005` | HIGH | `.env` попадает в репозиторий | git / .gitignore |
61
+ | `AIG010` | HIGH | SQL-инъекция: f-string, `%`, `+`, `.format()` в `execute()` / `text()` | Python |
62
+ | `AIG011` | HIGH | Shell-инъекция: `shell=True`, `os.system` с переменной | Python |
63
+ | `AIG012` | HIGH | `eval` / `exec` над динамическими данными | Python |
64
+ | `AIG013` | HIGH | `pickle.loads`, `yaml.load` без SafeLoader | Python |
65
+ | `AIG017` | HIGH | JWT без проверки подписи, `algorithms=["none"]` | Python |
66
+ | `AIG030` | HIGH | `eval` / `new Function` | JS / TS |
67
+ | `AIG014` | MEDIUM | `debug=True`, `DEBUG = True` | Python |
68
+ | `AIG015` | MEDIUM → HIGH | CORS `*` (HIGH вместе с `allow_credentials=True`) | FastAPI, Express |
69
+ | `AIG016` | MEDIUM | `verify=False` в requests / httpx | Python |
70
+ | `AIG019` | MEDIUM | Токены и OTP-коды через `random` вместо `secrets` | Python |
71
+ | `AIG031` | MEDIUM | XSS: `innerHTML`, `dangerouslySetInnerHTML`, `v-html` | JS / TS / Vue |
72
+ | `AIG032` | MEDIUM | Секреты в `NEXT_PUBLIC_*` / `VITE_*` / `REACT_APP_*` | фронтенд |
73
+ | `AIG042` | MEDIUM | Порт БД/Redis опубликован на `0.0.0.0` | docker-compose |
74
+ | `AIG018` | LOW | MD5 / SHA-1 | Python |
75
+ | `AIG021` | LOW | POST/PUT/PATCH/DELETE без `Depends(...)` авторизации | FastAPI |
76
+ | `AIG040` | LOW | Контейнер работает от root | Dockerfile |
77
+
78
+ Подробно про любое правило: `aigis explain AIG010`, полный список: `aigis rules`.
79
+
80
+ ## Использование
81
+
82
+ ```bash
83
+ aigis scan . # текстовый отчёт с объяснениями
84
+ aigis scan src api --quiet # несколько путей, без блоков «чем опасно / как исправить»
85
+ aigis scan . --min-severity medium # скрыть LOW
86
+ aigis scan . --fail-on critical # падать только на критичных
87
+ aigis scan . -f sarif -o aigis.sarif # для GitHub Code Scanning
88
+ aigis scan . -f json | jq '.findings[]' # для своих скриптов
89
+ aigis scan . -f markdown > SECURITY.md # отчёт для PR / заказчика
90
+ ```
91
+
92
+ | Exit code | Значение |
93
+ |---|---|
94
+ | `0` | нет находок уровня `--fail-on` (по умолчанию `high`) и выше |
95
+ | `1` | найдены проблемы уровня `--fail-on` и выше |
96
+ | `2` | ошибка запуска (неверный путь, неизвестное правило) |
97
+
98
+ В конце отчёта выводится **security score** (0–100) и оценка от `A` до `F`.
99
+
100
+ ### Исключения
101
+
102
+ ```python
103
+ eval(trusted_expr) # aigis: ignore
104
+ DEBUG = True # aigis: ignore[AIG014]
105
+ ```
106
+
107
+ Файлы и папки исключаются через `.aigisignore` (glob-шаблоны) или флаг `--exclude`.
108
+ `node_modules`, `.venv`, `dist` и lock-файлы пропускаются автоматически, а в git-репозитории ещё и всё из `.gitignore`.
109
+
110
+ ## Автоисправление: `aigis fix`
111
+
112
+ ```bash
113
+ aigis fix --dry-run # только показать diff
114
+ aigis fix # применить
115
+ ```
116
+
117
+ Исправляет только то, что можно поменять без риска сломать логику:
118
+
119
+ | Было | Стало |
120
+ |---|---|
121
+ | `BOT_TOKEN = "7312845567:AA..."` | `BOT_TOKEN = os.environ["BOT_TOKEN"]` + значение в `.env`, ключ в `.env.example` |
122
+ | `connect(password="Pa55word!")` | `connect(password=os.environ["PASSWORD"])` |
123
+ | `.env` не в `.gitignore` | `.env` добавлен в `.gitignore` |
124
+ | `requests.get(url, verify=False)` | `verify=True` |
125
+ | `yaml.load(data)` | `yaml.safe_load(data)` |
126
+ | `otp = random.randint(100000, 999999)` | `otp = secrets.randbelow(900000) + 100000` |
127
+ | `token = random.choice(alphabet)` | `secrets.choice(alphabet)` |
128
+ | `app.run(debug=True)` / `DEBUG = True` | `debug=False` / `DEBUG = os.getenv("DEBUG") == "1"` |
129
+ | `- "5432:5432"` в compose | `- "127.0.0.1:5432:5432"` |
130
+
131
+ Нужные `import os` / `import secrets` добавляются сами. SQL-инъекции, `eval`, `pickle`, JWT и CORS
132
+ автоматически не трогаются: там нужно понимать логику приложения, поэтому они остаются в списке «вручную».
133
+
134
+ ## Утёкшие ключи в истории: `aigis history`
135
+
136
+ ```text
137
+ $ aigis history
138
+ aigis history: секретов в истории git: 1
139
+
140
+ [CRITICAL] AIG001 7312************ (Telegram bot token)
141
+ bot.py · коммит 402c92b от 2026-09-14 · удалён, но остался в истории
142
+ ```
143
+
144
+ Удалить ключ новым коммитом мало: старая версия файла остаётся в истории, и её видит любой, кто склонировал репо.
145
+ `aigis history` проходит по всем коммитам и веткам, показывает, где и когда ключ появился и есть ли он в коде сейчас.
146
+ Ключи в отчёте замаскированы. Для CI: `aigis history -f json`, exit code `1`, если что-то найдено.
147
+
148
+ ## Бейдж для README
149
+
150
+ ```bash
151
+ aigis badge # пишет aigis-badge.svg с оценкой, например «aigis | A 100/100»
152
+ aigis badge --url # ссылка shields.io, без файла
153
+ ```
154
+
155
+ ## AI-режим
156
+
157
+ ```bash
158
+ export AIGIS_API_KEY=... # любой OpenAI-совместимый API
159
+ export AIGIS_BASE_URL=https://api.openai.com/v1 # или свой прокси / локальная LLM
160
+ export AIGIS_MODEL=gpt-4o-mini
161
+ aigis scan . --ai
162
+ ```
163
+
164
+ Для каждой находки модель получает фрагмент кода вокруг неё и возвращает исправление именно под твой код.
165
+ **Находки с секретами (`AIG001`–`AIG005`) в модель никогда не отправляются.** Если ключа нет или API недоступен,
166
+ сканер предупредит об этом и покажет стандартные советы.
167
+
168
+ ## GitHub Actions
169
+
170
+ ```yaml
171
+ name: security
172
+ on: [push, pull_request]
173
+
174
+ jobs:
175
+ aigis:
176
+ runs-on: ubuntu-latest
177
+ permissions:
178
+ contents: read
179
+ security-events: write
180
+ steps:
181
+ - uses: actions/checkout@v4
182
+ - uses: jasperBLCK/AigisSAST@v0.2.0
183
+ with:
184
+ fail-on: high
185
+ ```
186
+
187
+ Находки появятся во вкладке **Security → Code scanning** и прямо в diff пулл-реквеста.
188
+
189
+ ## pre-commit
190
+
191
+ ```yaml
192
+ repos:
193
+ - repo: https://github.com/jasperBLCK/AigisSAST
194
+ rev: v0.2.0
195
+ hooks:
196
+ - id: aigis
197
+ ```
198
+
199
+ ## Как устроено
200
+
201
+ ```text
202
+ aigis scan
203
+ │
204
+ ├── scanner git ls-files → фильтры → .aigisignore → чтение файлов
205
+ ├── checks/
206
+ │ ├── python_ast ast.NodeVisitor: SQL, shell, eval, pickle, JWT, CORS, debug, random, FastAPI auth
207
+ │ └── text regex: токены, ключи, DSN, конфиги, Dockerfile, compose, JS/TS
208
+ ├── ai (опционально) OpenAI-совместимый API, секреты отфильтрованы
209
+ └── report text · json · sarif · markdown + score / grade
210
+
211
+ aigis fix AST-позиции находок → точечные правки → .env / .env.example / .gitignore
212
+ aigis history git log -p --all → добавленные строки → те же детекторы секретов
213
+ aigis badge score / grade → SVG или shields.io
214
+ ```
215
+
216
+ Разработка:
217
+
218
+ ```bash
219
+ python -m venv .venv && . .venv/bin/activate
220
+ pip install -e ".[dev]"
221
+ ruff check . && ruff format --check . && pytest -q
222
+ ```
223
+
224
+ ## Roadmap
225
+
226
+ - [x] Python AST-правила, секреты, конфиги, Dockerfile, compose, JS/TS
227
+ - [x] SARIF, JSON, Markdown, GitHub Action, pre-commit
228
+ - [x] AI-исправления через OpenAI-совместимый API
229
+ - [x] `aigis fix`: автоматическое исправление безопасных случаев (вынос секретов в `.env`)
230
+ - [x] `aigis history`: поиск секретов во всей истории git
231
+ - [x] `aigis badge`: бейдж с оценкой безопасности
232
+ - [ ] Проверка зависимостей на известные CVE и тайпсквоттинг
233
+ - [ ] Правила для Django и Flask, Go и PHP
234
+ - [ ] VS Code extension
235
+
236
+ ## License
237
+
238
+ [MIT](https://github.com/jasperBLCK/AigisSAST/blob/main/LICENSE)
239
+
240
+ <img src="https://raw.githubusercontent.com/jasperBLCK/AigisSAST/main/assets/divider.svg" width="100%"/>
241
+
242
+ <div align="center"><sub>built by <a href="https://github.com/jasperBLCK">jasperBLCK</a> · ship fast, ship secure</sub></div>
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aigis-sast"
7
+ version = "0.2.0"
8
+ description = "AigisSAST: security scanner for AI-generated code. Finds what vibe coding leaves behind and explains how to fix it"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [{ name = "jasperBLCK" }]
14
+ keywords = ["security", "sast", "linter", "secrets", "ai", "vibe-coding", "fastapi"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Security",
21
+ "Topic :: Software Development :: Quality Assurance",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.optional-dependencies]
26
+ dev = ["pytest>=8", "ruff>=0.6"]
27
+
28
+ [project.scripts]
29
+ aigis = "aigis.cli:main"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/jasperBLCK/AigisSAST"
33
+ Source = "https://github.com/jasperBLCK/AigisSAST"
34
+ Issues = "https://github.com/jasperBLCK/AigisSAST/issues"
35
+ Changelog = "https://github.com/jasperBLCK/AigisSAST/releases"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.ruff]
41
+ line-length = 120
42
+ target-version = "py39"
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E", "F", "W", "I", "B", "UP", "SIM"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -0,0 +1,3 @@
1
+ from aigis.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import urllib.error
6
+ import urllib.request
7
+ from pathlib import Path
8
+
9
+ from aigis.models import Finding
10
+
11
+ SECRET_RULES = {"AIG001", "AIG002", "AIG003", "AIG004", "AIG005"}
12
+ PROMPT = (
13
+ "Ты senior security-инженер. Ниже уязвимый фрагмент кода и найденная проблема. "
14
+ "Ответь по-русски, коротко и простыми словами: 1) одной фразой, как это можно эксплуатировать "
15
+ "именно здесь; 2) исправленный код этого фрагмента. Без вступлений."
16
+ )
17
+
18
+
19
+ class AIConfig:
20
+ def __init__(self) -> None:
21
+ self.api_key = os.environ.get("AIGIS_API_KEY", "")
22
+ self.base_url = os.environ.get("AIGIS_BASE_URL", "https://api.openai.com/v1").rstrip("/")
23
+ self.model = os.environ.get("AIGIS_MODEL", "gpt-4o-mini")
24
+
25
+ @property
26
+ def enabled(self) -> bool:
27
+ return bool(self.api_key)
28
+
29
+
30
+ def _context(root: Path, finding: Finding, radius: int = 6) -> str:
31
+ try:
32
+ lines = (root / finding.path).read_text("utf-8", "replace").splitlines()
33
+ except OSError:
34
+ return finding.snippet
35
+ start = max(0, finding.line - 1 - radius)
36
+ chunk = lines[start : finding.line + radius]
37
+ return "\n".join(f"{start + i + 1:>4} {ln}" for i, ln in enumerate(chunk))
38
+
39
+
40
+ def _ask(cfg: AIConfig, content: str, timeout: float) -> str:
41
+ body = json.dumps(
42
+ {
43
+ "model": cfg.model,
44
+ "temperature": 0.2,
45
+ "messages": [{"role": "system", "content": PROMPT}, {"role": "user", "content": content}],
46
+ }
47
+ ).encode()
48
+ req = urllib.request.Request(
49
+ f"{cfg.base_url}/chat/completions",
50
+ data=body,
51
+ headers={"Authorization": f"Bearer {cfg.api_key}", "Content-Type": "application/json"},
52
+ )
53
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
54
+ data = json.loads(resp.read())
55
+ return str(data["choices"][0]["message"]["content"]).strip()
56
+
57
+
58
+ def enrich(root: Path, findings: list[Finding], *, limit: int = 15, timeout: float = 30.0) -> str | None:
59
+ """Add AI-written, context-specific fixes. Secret findings are never sent to the model."""
60
+ cfg = AIConfig()
61
+ if not cfg.enabled:
62
+ return "AIGIS_API_KEY не задан: AI-режим пропущен, показаны стандартные советы."
63
+ for f in [f for f in findings if f.rule.id not in SECRET_RULES][:limit]:
64
+ content = f"Проблема: {f.rule.id} {f.rule.title}\nФайл: {f.path}:{f.line}\n\n{_context(root, f)}"
65
+ try:
66
+ f.ai_fix = _ask(cfg, content, timeout)
67
+ except (urllib.error.URLError, TimeoutError, KeyError, ValueError) as exc:
68
+ return f"AI-режим недоступен ({exc}); показаны стандартные советы."
69
+ return None