vigilsec 0.1.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.
- vigilsec-0.1.0/LICENSE +73 -0
- vigilsec-0.1.0/PKG-INFO +290 -0
- vigilsec-0.1.0/README.md +276 -0
- vigilsec-0.1.0/pyproject.toml +36 -0
- vigilsec-0.1.0/setup.cfg +4 -0
- vigilsec-0.1.0/src/vigil/__init__.py +2 -0
- vigilsec-0.1.0/src/vigil/cli.py +175 -0
- vigilsec-0.1.0/src/vigil/config.py +40 -0
- vigilsec-0.1.0/src/vigil/engine.py +68 -0
- vigilsec-0.1.0/src/vigil/reporter.py +119 -0
- vigilsec-0.1.0/src/vigil/rules/__init__.py +103 -0
- vigilsec-0.1.0/src/vigil/rules/agency.py +174 -0
- vigilsec-0.1.0/src/vigil/rules/base.py +48 -0
- vigilsec-0.1.0/src/vigil/rules/deps.py +122 -0
- vigilsec-0.1.0/src/vigil/rules/docker.py +145 -0
- vigilsec-0.1.0/src/vigil/rules/dockerfile.py +130 -0
- vigilsec-0.1.0/src/vigil/rules/iam.py +101 -0
- vigilsec-0.1.0/src/vigil/rules/k8s.py +75 -0
- vigilsec-0.1.0/src/vigil/rules/mcp_security.py +147 -0
- vigilsec-0.1.0/src/vigil/rules/nginx.py +85 -0
- vigilsec-0.1.0/src/vigil/rules/prompt_injection.py +185 -0
- vigilsec-0.1.0/src/vigil/rules/secrets.py +143 -0
- vigilsec-0.1.0/src/vigil/rules/shell.py +96 -0
- vigilsec-0.1.0/src/vigil/rules/trivy.py +62 -0
- vigilsec-0.1.0/src/vigil/telemetry.py +78 -0
- vigilsec-0.1.0/src/vigilsec.egg-info/PKG-INFO +290 -0
- vigilsec-0.1.0/src/vigilsec.egg-info/SOURCES.txt +45 -0
- vigilsec-0.1.0/src/vigilsec.egg-info/dependency_links.txt +1 -0
- vigilsec-0.1.0/src/vigilsec.egg-info/entry_points.txt +2 -0
- vigilsec-0.1.0/src/vigilsec.egg-info/top_level.txt +1 -0
- vigilsec-0.1.0/tests/test_cli_init.py +59 -0
- vigilsec-0.1.0/tests/test_config.py +91 -0
- vigilsec-0.1.0/tests/test_engine.py +91 -0
- vigilsec-0.1.0/tests/test_reporter.py +81 -0
- vigilsec-0.1.0/tests/test_rules_agency.py +97 -0
- vigilsec-0.1.0/tests/test_rules_docker.py +219 -0
- vigilsec-0.1.0/tests/test_rules_dockerfile.py +71 -0
- vigilsec-0.1.0/tests/test_rules_iam.py +129 -0
- vigilsec-0.1.0/tests/test_rules_k8s.py +121 -0
- vigilsec-0.1.0/tests/test_rules_mcp.py +100 -0
- vigilsec-0.1.0/tests/test_rules_nginx.py +95 -0
- vigilsec-0.1.0/tests/test_rules_prompt_injection.py +118 -0
- vigilsec-0.1.0/tests/test_rules_secrets.py +79 -0
- vigilsec-0.1.0/tests/test_rules_secrets_extended.py +95 -0
- vigilsec-0.1.0/tests/test_rules_shell.py +91 -0
- vigilsec-0.1.0/tests/test_rules_trivy.py +89 -0
- vigilsec-0.1.0/tests/test_telemetry.py +148 -0
vigilsec-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: Prem Kumar Akula
|
|
6
|
+
Licensed Work: Vigil
|
|
7
|
+
The Licensed Work is (c) 2026 Prem Kumar Akula
|
|
8
|
+
Additional Use Grant: You may make production use of the Licensed Work,
|
|
9
|
+
provided such use does not include offering the Licensed
|
|
10
|
+
Work to third parties on a hosted or embedded basis in
|
|
11
|
+
order to compete with Vigil's paid commercial offerings.
|
|
12
|
+
Change Date: Four years from the date the specific Licensed Work
|
|
13
|
+
version is first publicly distributed under this License.
|
|
14
|
+
Change License: MIT License
|
|
15
|
+
|
|
16
|
+
For information about alternative licensing arrangements for the Licensed
|
|
17
|
+
Work, please contact: prem.fwss@gmail.com
|
|
18
|
+
|
|
19
|
+
Notice
|
|
20
|
+
|
|
21
|
+
The Business Source License (this document, or the "License") is not an Open
|
|
22
|
+
Source License, as defined by the Open Source Initiative. However, the
|
|
23
|
+
Licensed Work will eventually be made available under an Open Source License,
|
|
24
|
+
as stated in this License.
|
|
25
|
+
|
|
26
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
27
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
28
|
+
|
|
29
|
+
-----------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
Business Source License 1.1
|
|
32
|
+
|
|
33
|
+
Terms
|
|
34
|
+
|
|
35
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
36
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
37
|
+
Licensor may make an Additional Use Grant, above, permitting limited
|
|
38
|
+
production use.
|
|
39
|
+
|
|
40
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
41
|
+
available distribution of a specific version of the Licensed Work under this
|
|
42
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
43
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
44
|
+
above terminate.
|
|
45
|
+
|
|
46
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
47
|
+
currently in effect as described in this License, you must purchase a
|
|
48
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
49
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
50
|
+
|
|
51
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
52
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
53
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
54
|
+
for each version of the Licensed Work released by Licensor.
|
|
55
|
+
|
|
56
|
+
You must conspicuously display this License on each original or modified copy
|
|
57
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
58
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
59
|
+
License apply to your use of that work.
|
|
60
|
+
|
|
61
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
62
|
+
terminate your rights under this License for the current and all other
|
|
63
|
+
versions of the Licensed Work.
|
|
64
|
+
|
|
65
|
+
This License does not grant you any right in any trademark or logo of
|
|
66
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
67
|
+
Licensor as expressly required by this License).
|
|
68
|
+
|
|
69
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
70
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
71
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
72
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
73
|
+
TITLE.
|
vigilsec-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vigilsec
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI coding security co-pilot — blocks insecure code at generation time
|
|
5
|
+
Author-email: Prem Kumar Akula <prem.fwss@gmail.com>
|
|
6
|
+
License-Expression: BUSL-1.1
|
|
7
|
+
Project-URL: Homepage, https://thefwss.com/vigil
|
|
8
|
+
Project-URL: Feedback, https://thefwss.com/vigil
|
|
9
|
+
Keywords: security,devsecops,docker,ai,claude,static-analysis,linter
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Vigil
|
|
16
|
+
|
|
17
|
+
**AI coding security co-pilot — blocks insecure code at the moment of generation.**
|
|
18
|
+
|
|
19
|
+
Vigil intercepts every file an AI coding assistant writes and blocks it if CRITICAL or HIGH security findings are detected — before the file hits disk. It's the only tool that operates at generation time rather than post-commit.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
AI writes file → vigil scan → exit 2 → Claude Code blocks the write
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## The Problem
|
|
28
|
+
|
|
29
|
+
AI coding assistants reproduce the most common patterns in their training data. The most common patterns are insecure defaults.
|
|
30
|
+
|
|
31
|
+
The clearest example: every existing IaC scanner (Checkov, Trivy, Snyk, Semgrep) misses the docker-compose port binding that exposes your database to the internet:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
ports:
|
|
35
|
+
- "5432:5432" # ← binds to 0.0.0.0, bypasses UFW, reachable from anywhere
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The correct form is `"127.0.0.1:5432:5432"`. Vigil catches it. Nothing else does.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install vigilsec
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Wire the Claude Code hook (one time):**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
vigil init --global
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That's it. Every file Claude Code writes is now scanned before it saves. Reload Claude Code to activate.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Scan a single file
|
|
62
|
+
vigil scan docker-compose.yml
|
|
63
|
+
|
|
64
|
+
# Scan a directory
|
|
65
|
+
vigil scan ./my-project/
|
|
66
|
+
|
|
67
|
+
# JSON output (for CI / dashboards)
|
|
68
|
+
vigil scan ./my-project/ --format json
|
|
69
|
+
|
|
70
|
+
# SARIF output (for GitHub Advanced Security)
|
|
71
|
+
vigil scan ./my-project/ --format sarif > results.sarif
|
|
72
|
+
|
|
73
|
+
# Only report HIGH and above
|
|
74
|
+
vigil scan ./my-project/ --severity HIGH
|
|
75
|
+
|
|
76
|
+
# Open feedback & waitlist form
|
|
77
|
+
vigil feedback
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Exit codes:**
|
|
81
|
+
|
|
82
|
+
| Code | Meaning |
|
|
83
|
+
|------|---------|
|
|
84
|
+
| `0` | No findings — write proceeds |
|
|
85
|
+
| `1` | Advisory findings only (MEDIUM / LOW / INFO) |
|
|
86
|
+
| `2` | CRITICAL or HIGH found — **Claude Code blocks the write** |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Rules
|
|
91
|
+
|
|
92
|
+
35 rules across 9 categories. All built-in, stdlib-only, zero runtime dependencies.
|
|
93
|
+
|
|
94
|
+
### Secrets & Injection (10 rules)
|
|
95
|
+
|
|
96
|
+
| Rule | Severity | What it catches |
|
|
97
|
+
|------|----------|----------------|
|
|
98
|
+
| VGL-S001 | CRITICAL | Hardcoded AWS / cloud API keys |
|
|
99
|
+
| VGL-S002 | CRITICAL | Hardcoded passwords (`password =`, `passwd =`) |
|
|
100
|
+
| VGL-S003 | HIGH | Generic API key / token assignments |
|
|
101
|
+
| VGL-S004 | HIGH | Generic secret / credential assignments |
|
|
102
|
+
| VGL-S005 | CRITICAL | JWT signing secrets |
|
|
103
|
+
| VGL-S006 | CRITICAL | PEM private keys |
|
|
104
|
+
| VGL-S007 | CRITICAL | Credential-embedded database URLs (`postgres://user:pass@host`) |
|
|
105
|
+
| VGL-S008 | CRITICAL | Stripe live keys (`sk_live_...`) |
|
|
106
|
+
| VGL-S009 | CRITICAL | Slack tokens (`xoxb-`, `xoxp-`) |
|
|
107
|
+
| VGL-S010 | CRITICAL | OpenAI, GitHub, GitLab, Google provider keys |
|
|
108
|
+
| VGL-I001 | CRITICAL | `eval()` with variable input |
|
|
109
|
+
| VGL-I002 | HIGH | `subprocess(shell=True)` with variable input |
|
|
110
|
+
| VGL-I003 | HIGH | `os.system()` with variable input |
|
|
111
|
+
|
|
112
|
+
### Docker IaC (2 rules)
|
|
113
|
+
|
|
114
|
+
| Rule | Severity | What it catches |
|
|
115
|
+
|------|----------|----------------|
|
|
116
|
+
| VGL-D001 | CRITICAL | `"PORT:PORT"` binding — bypasses UFW, exposes to internet |
|
|
117
|
+
| VGL-D002 | HIGH | Hardcoded secrets in `environment:` blocks |
|
|
118
|
+
|
|
119
|
+
### Dockerfile Hardening (3 rules)
|
|
120
|
+
|
|
121
|
+
| Rule | Severity | What it catches |
|
|
122
|
+
|------|----------|----------------|
|
|
123
|
+
| VGL-DF001 | HIGH | Container running as root (no `USER` directive) |
|
|
124
|
+
| VGL-DF002 | MEDIUM | Unpinned `:latest` base image |
|
|
125
|
+
| VGL-DF003 | CRITICAL | Secrets baked into image layers via `ENV`/`ARG` |
|
|
126
|
+
|
|
127
|
+
### nginx (1 rule)
|
|
128
|
+
|
|
129
|
+
| Rule | Severity | What it catches |
|
|
130
|
+
|------|----------|----------------|
|
|
131
|
+
| VGL-N001 | HIGH | Missing security headers, `server_tokens on`, deprecated TLS |
|
|
132
|
+
|
|
133
|
+
### Kubernetes (1 rule)
|
|
134
|
+
|
|
135
|
+
| Rule | Severity | What it catches |
|
|
136
|
+
|------|----------|----------------|
|
|
137
|
+
| VGL-K001 | CRITICAL/HIGH | `privileged: true`, `hostNetwork/hostPID/hostIPC: true` |
|
|
138
|
+
|
|
139
|
+
### IAM Policies (1 rule)
|
|
140
|
+
|
|
141
|
+
| Rule | Severity | What it catches |
|
|
142
|
+
|------|----------|----------------|
|
|
143
|
+
| VGL-IAM001 | CRITICAL/HIGH | `"Action": "*"` and `"Resource": "*"` wildcards |
|
|
144
|
+
|
|
145
|
+
### AI Agent Patterns (7 rules)
|
|
146
|
+
|
|
147
|
+
New category — catches the security anti-patterns unique to AI-generated agentic code.
|
|
148
|
+
|
|
149
|
+
| Rule | Severity | What it catches |
|
|
150
|
+
|------|----------|----------------|
|
|
151
|
+
| VGL-A001 | CRITICAL | LLM output piped to `subprocess.run()` / `os.system()` |
|
|
152
|
+
| VGL-A002 | HIGH | Hardcoded `auto_approve = True` / `skip_confirmation = True` |
|
|
153
|
+
| VGL-A003 | HIGH | Unbounded `while True` loop making LLM calls with no iteration cap |
|
|
154
|
+
| VGL-A004 | HIGH | LLM response content written directly to filesystem |
|
|
155
|
+
| VGL-PI001 | CRITICAL | User input embedded in system prompt |
|
|
156
|
+
| VGL-PI002 | HIGH | Raw `request.body` passed as LLM message content |
|
|
157
|
+
| VGL-PI003 | HIGH | `str.format()` on `system_prompt` variables with user-controlled data |
|
|
158
|
+
| VGL-PI004 | MEDIUM | Unsanitized tool output appended to conversation |
|
|
159
|
+
|
|
160
|
+
### MCP Server Security (3 rules)
|
|
161
|
+
|
|
162
|
+
| Rule | Severity | What it catches |
|
|
163
|
+
|------|----------|----------------|
|
|
164
|
+
| VGL-MCP001 | CRITICAL | Injection strings in tool descriptions (`ignore previous instructions`) |
|
|
165
|
+
| VGL-MCP002 | HIGH | Dynamic tool descriptions built from user-controlled data |
|
|
166
|
+
| VGL-MCP003 | HIGH | Shell execution inside MCP handlers without a sandbox |
|
|
167
|
+
|
|
168
|
+
### Shell Scripts (1 rule)
|
|
169
|
+
|
|
170
|
+
| Rule | Severity | What it catches |
|
|
171
|
+
|------|----------|----------------|
|
|
172
|
+
| VGL-S011 | HIGH | Secret variable passed inline to subprocess or SSH command — visible in `ps aux` on both machines |
|
|
173
|
+
|
|
174
|
+
### Dependency CVEs (2 rules)
|
|
175
|
+
|
|
176
|
+
| Rule | Severity | What it catches |
|
|
177
|
+
|------|----------|----------------|
|
|
178
|
+
| VGL-DEP001 | HIGH | Python CVEs via `pip-audit` (runs on every `requirements.txt` change) |
|
|
179
|
+
| VGL-DEP002 | HIGH | npm CVEs via `npm audit` (runs on every `package.json` change) |
|
|
180
|
+
|
|
181
|
+
### Trivy IaC Deep Scan (1 rule)
|
|
182
|
+
|
|
183
|
+
| Rule | Severity | What it catches |
|
|
184
|
+
|------|----------|----------------|
|
|
185
|
+
| VGL-T001 | HIGH | Dockerfile and Terraform misconfigurations via Trivy |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
Place a `.vigilrc` file in your project root (or any ancestor directory):
|
|
192
|
+
|
|
193
|
+
```toml
|
|
194
|
+
# .vigilrc
|
|
195
|
+
disabled_rules = ["VGL-T001"] # skip trivy scan for this project
|
|
196
|
+
min_severity = "HIGH" # only report HIGH and above
|
|
197
|
+
exclude_paths = ["vendor", "legacy"]
|
|
198
|
+
telemetry = false # opt out of anonymous local telemetry
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Vigil walks up the directory tree to find the nearest `.vigilrc`. Child config always wins over parent. Monorepos can have per-project overrides alongside a workspace default.
|
|
202
|
+
|
|
203
|
+
**Inline suppression** — for a specific line you've reviewed and accepted:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
auto_approve = True # vigil: ignore
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Same pattern as `# noqa` (flake8) and `# nosec` (bandit).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Opt-out
|
|
214
|
+
|
|
215
|
+
Vigil collects anonymous, local-only telemetry: rule ID, severity, and file extension. No file paths, no code, no identifiable data. Stored at `~/.vigil/events.jsonl` — never sent anywhere.
|
|
216
|
+
|
|
217
|
+
Opt out permanently:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
export VIGIL_NO_TELEMETRY=1
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Or in `.vigilrc`:
|
|
224
|
+
|
|
225
|
+
```toml
|
|
226
|
+
telemetry = false
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Adding a Rule
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# src/vigil/rules/my_category.py
|
|
235
|
+
from pathlib import Path
|
|
236
|
+
from .base import Finding, Rule, Severity
|
|
237
|
+
|
|
238
|
+
class MyRule(Rule):
|
|
239
|
+
id = "VGL-X001"
|
|
240
|
+
name = "Descriptive rule name"
|
|
241
|
+
severity = Severity.HIGH
|
|
242
|
+
|
|
243
|
+
def applies_to(self, path: Path) -> bool:
|
|
244
|
+
return path.suffix == ".yml"
|
|
245
|
+
|
|
246
|
+
def check(self, path: Path) -> list[Finding]:
|
|
247
|
+
findings = []
|
|
248
|
+
for i, line in enumerate(path.read_text().splitlines(), 1):
|
|
249
|
+
if "bad_pattern" in line:
|
|
250
|
+
findings.append(Finding(
|
|
251
|
+
rule_id=self.id,
|
|
252
|
+
severity=self.severity,
|
|
253
|
+
message="Found bad pattern",
|
|
254
|
+
file_path=path,
|
|
255
|
+
line=i,
|
|
256
|
+
snippet=line.strip(),
|
|
257
|
+
fix="Do this instead.",
|
|
258
|
+
))
|
|
259
|
+
return findings
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Then add it to `DEFAULT_RULES` in `src/vigil/rules/__init__.py`. Write tests. Done.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Development
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
git clone http://your-gitea/fwss/vigil.git
|
|
270
|
+
cd vigil
|
|
271
|
+
python3 -m venv venv && source venv/bin/activate
|
|
272
|
+
pip install -e ".[dev]"
|
|
273
|
+
pytest tests/ -v
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
[Business Source License 1.1](LICENSE) — free for non-commercial use. Commercial use requires a license agreement. Converts to MIT on 2030-06-26.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Feedback & Waitlist
|
|
285
|
+
|
|
286
|
+
Found a false positive? Want a rule that doesn't exist yet? Building with AI agents and hitting patterns Vigil should catch?
|
|
287
|
+
|
|
288
|
+
[Join the waitlist → thefwss.com/vigil](https://thefwss.com/vigil)
|
|
289
|
+
|
|
290
|
+
Or: `vigil feedback`
|
vigilsec-0.1.0/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Vigil
|
|
2
|
+
|
|
3
|
+
**AI coding security co-pilot — blocks insecure code at the moment of generation.**
|
|
4
|
+
|
|
5
|
+
Vigil intercepts every file an AI coding assistant writes and blocks it if CRITICAL or HIGH security findings are detected — before the file hits disk. It's the only tool that operates at generation time rather than post-commit.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
AI writes file → vigil scan → exit 2 → Claude Code blocks the write
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## The Problem
|
|
14
|
+
|
|
15
|
+
AI coding assistants reproduce the most common patterns in their training data. The most common patterns are insecure defaults.
|
|
16
|
+
|
|
17
|
+
The clearest example: every existing IaC scanner (Checkov, Trivy, Snyk, Semgrep) misses the docker-compose port binding that exposes your database to the internet:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
ports:
|
|
21
|
+
- "5432:5432" # ← binds to 0.0.0.0, bypasses UFW, reachable from anywhere
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The correct form is `"127.0.0.1:5432:5432"`. Vigil catches it. Nothing else does.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install vigilsec
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Wire the Claude Code hook (one time):**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
vigil init --global
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That's it. Every file Claude Code writes is now scanned before it saves. Reload Claude Code to activate.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Scan a single file
|
|
48
|
+
vigil scan docker-compose.yml
|
|
49
|
+
|
|
50
|
+
# Scan a directory
|
|
51
|
+
vigil scan ./my-project/
|
|
52
|
+
|
|
53
|
+
# JSON output (for CI / dashboards)
|
|
54
|
+
vigil scan ./my-project/ --format json
|
|
55
|
+
|
|
56
|
+
# SARIF output (for GitHub Advanced Security)
|
|
57
|
+
vigil scan ./my-project/ --format sarif > results.sarif
|
|
58
|
+
|
|
59
|
+
# Only report HIGH and above
|
|
60
|
+
vigil scan ./my-project/ --severity HIGH
|
|
61
|
+
|
|
62
|
+
# Open feedback & waitlist form
|
|
63
|
+
vigil feedback
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Exit codes:**
|
|
67
|
+
|
|
68
|
+
| Code | Meaning |
|
|
69
|
+
|------|---------|
|
|
70
|
+
| `0` | No findings — write proceeds |
|
|
71
|
+
| `1` | Advisory findings only (MEDIUM / LOW / INFO) |
|
|
72
|
+
| `2` | CRITICAL or HIGH found — **Claude Code blocks the write** |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Rules
|
|
77
|
+
|
|
78
|
+
35 rules across 9 categories. All built-in, stdlib-only, zero runtime dependencies.
|
|
79
|
+
|
|
80
|
+
### Secrets & Injection (10 rules)
|
|
81
|
+
|
|
82
|
+
| Rule | Severity | What it catches |
|
|
83
|
+
|------|----------|----------------|
|
|
84
|
+
| VGL-S001 | CRITICAL | Hardcoded AWS / cloud API keys |
|
|
85
|
+
| VGL-S002 | CRITICAL | Hardcoded passwords (`password =`, `passwd =`) |
|
|
86
|
+
| VGL-S003 | HIGH | Generic API key / token assignments |
|
|
87
|
+
| VGL-S004 | HIGH | Generic secret / credential assignments |
|
|
88
|
+
| VGL-S005 | CRITICAL | JWT signing secrets |
|
|
89
|
+
| VGL-S006 | CRITICAL | PEM private keys |
|
|
90
|
+
| VGL-S007 | CRITICAL | Credential-embedded database URLs (`postgres://user:pass@host`) |
|
|
91
|
+
| VGL-S008 | CRITICAL | Stripe live keys (`sk_live_...`) |
|
|
92
|
+
| VGL-S009 | CRITICAL | Slack tokens (`xoxb-`, `xoxp-`) |
|
|
93
|
+
| VGL-S010 | CRITICAL | OpenAI, GitHub, GitLab, Google provider keys |
|
|
94
|
+
| VGL-I001 | CRITICAL | `eval()` with variable input |
|
|
95
|
+
| VGL-I002 | HIGH | `subprocess(shell=True)` with variable input |
|
|
96
|
+
| VGL-I003 | HIGH | `os.system()` with variable input |
|
|
97
|
+
|
|
98
|
+
### Docker IaC (2 rules)
|
|
99
|
+
|
|
100
|
+
| Rule | Severity | What it catches |
|
|
101
|
+
|------|----------|----------------|
|
|
102
|
+
| VGL-D001 | CRITICAL | `"PORT:PORT"` binding — bypasses UFW, exposes to internet |
|
|
103
|
+
| VGL-D002 | HIGH | Hardcoded secrets in `environment:` blocks |
|
|
104
|
+
|
|
105
|
+
### Dockerfile Hardening (3 rules)
|
|
106
|
+
|
|
107
|
+
| Rule | Severity | What it catches |
|
|
108
|
+
|------|----------|----------------|
|
|
109
|
+
| VGL-DF001 | HIGH | Container running as root (no `USER` directive) |
|
|
110
|
+
| VGL-DF002 | MEDIUM | Unpinned `:latest` base image |
|
|
111
|
+
| VGL-DF003 | CRITICAL | Secrets baked into image layers via `ENV`/`ARG` |
|
|
112
|
+
|
|
113
|
+
### nginx (1 rule)
|
|
114
|
+
|
|
115
|
+
| Rule | Severity | What it catches |
|
|
116
|
+
|------|----------|----------------|
|
|
117
|
+
| VGL-N001 | HIGH | Missing security headers, `server_tokens on`, deprecated TLS |
|
|
118
|
+
|
|
119
|
+
### Kubernetes (1 rule)
|
|
120
|
+
|
|
121
|
+
| Rule | Severity | What it catches |
|
|
122
|
+
|------|----------|----------------|
|
|
123
|
+
| VGL-K001 | CRITICAL/HIGH | `privileged: true`, `hostNetwork/hostPID/hostIPC: true` |
|
|
124
|
+
|
|
125
|
+
### IAM Policies (1 rule)
|
|
126
|
+
|
|
127
|
+
| Rule | Severity | What it catches |
|
|
128
|
+
|------|----------|----------------|
|
|
129
|
+
| VGL-IAM001 | CRITICAL/HIGH | `"Action": "*"` and `"Resource": "*"` wildcards |
|
|
130
|
+
|
|
131
|
+
### AI Agent Patterns (7 rules)
|
|
132
|
+
|
|
133
|
+
New category — catches the security anti-patterns unique to AI-generated agentic code.
|
|
134
|
+
|
|
135
|
+
| Rule | Severity | What it catches |
|
|
136
|
+
|------|----------|----------------|
|
|
137
|
+
| VGL-A001 | CRITICAL | LLM output piped to `subprocess.run()` / `os.system()` |
|
|
138
|
+
| VGL-A002 | HIGH | Hardcoded `auto_approve = True` / `skip_confirmation = True` |
|
|
139
|
+
| VGL-A003 | HIGH | Unbounded `while True` loop making LLM calls with no iteration cap |
|
|
140
|
+
| VGL-A004 | HIGH | LLM response content written directly to filesystem |
|
|
141
|
+
| VGL-PI001 | CRITICAL | User input embedded in system prompt |
|
|
142
|
+
| VGL-PI002 | HIGH | Raw `request.body` passed as LLM message content |
|
|
143
|
+
| VGL-PI003 | HIGH | `str.format()` on `system_prompt` variables with user-controlled data |
|
|
144
|
+
| VGL-PI004 | MEDIUM | Unsanitized tool output appended to conversation |
|
|
145
|
+
|
|
146
|
+
### MCP Server Security (3 rules)
|
|
147
|
+
|
|
148
|
+
| Rule | Severity | What it catches |
|
|
149
|
+
|------|----------|----------------|
|
|
150
|
+
| VGL-MCP001 | CRITICAL | Injection strings in tool descriptions (`ignore previous instructions`) |
|
|
151
|
+
| VGL-MCP002 | HIGH | Dynamic tool descriptions built from user-controlled data |
|
|
152
|
+
| VGL-MCP003 | HIGH | Shell execution inside MCP handlers without a sandbox |
|
|
153
|
+
|
|
154
|
+
### Shell Scripts (1 rule)
|
|
155
|
+
|
|
156
|
+
| Rule | Severity | What it catches |
|
|
157
|
+
|------|----------|----------------|
|
|
158
|
+
| VGL-S011 | HIGH | Secret variable passed inline to subprocess or SSH command — visible in `ps aux` on both machines |
|
|
159
|
+
|
|
160
|
+
### Dependency CVEs (2 rules)
|
|
161
|
+
|
|
162
|
+
| Rule | Severity | What it catches |
|
|
163
|
+
|------|----------|----------------|
|
|
164
|
+
| VGL-DEP001 | HIGH | Python CVEs via `pip-audit` (runs on every `requirements.txt` change) |
|
|
165
|
+
| VGL-DEP002 | HIGH | npm CVEs via `npm audit` (runs on every `package.json` change) |
|
|
166
|
+
|
|
167
|
+
### Trivy IaC Deep Scan (1 rule)
|
|
168
|
+
|
|
169
|
+
| Rule | Severity | What it catches |
|
|
170
|
+
|------|----------|----------------|
|
|
171
|
+
| VGL-T001 | HIGH | Dockerfile and Terraform misconfigurations via Trivy |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
Place a `.vigilrc` file in your project root (or any ancestor directory):
|
|
178
|
+
|
|
179
|
+
```toml
|
|
180
|
+
# .vigilrc
|
|
181
|
+
disabled_rules = ["VGL-T001"] # skip trivy scan for this project
|
|
182
|
+
min_severity = "HIGH" # only report HIGH and above
|
|
183
|
+
exclude_paths = ["vendor", "legacy"]
|
|
184
|
+
telemetry = false # opt out of anonymous local telemetry
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Vigil walks up the directory tree to find the nearest `.vigilrc`. Child config always wins over parent. Monorepos can have per-project overrides alongside a workspace default.
|
|
188
|
+
|
|
189
|
+
**Inline suppression** — for a specific line you've reviewed and accepted:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
auto_approve = True # vigil: ignore
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Same pattern as `# noqa` (flake8) and `# nosec` (bandit).
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Opt-out
|
|
200
|
+
|
|
201
|
+
Vigil collects anonymous, local-only telemetry: rule ID, severity, and file extension. No file paths, no code, no identifiable data. Stored at `~/.vigil/events.jsonl` — never sent anywhere.
|
|
202
|
+
|
|
203
|
+
Opt out permanently:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
export VIGIL_NO_TELEMETRY=1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or in `.vigilrc`:
|
|
210
|
+
|
|
211
|
+
```toml
|
|
212
|
+
telemetry = false
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Adding a Rule
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# src/vigil/rules/my_category.py
|
|
221
|
+
from pathlib import Path
|
|
222
|
+
from .base import Finding, Rule, Severity
|
|
223
|
+
|
|
224
|
+
class MyRule(Rule):
|
|
225
|
+
id = "VGL-X001"
|
|
226
|
+
name = "Descriptive rule name"
|
|
227
|
+
severity = Severity.HIGH
|
|
228
|
+
|
|
229
|
+
def applies_to(self, path: Path) -> bool:
|
|
230
|
+
return path.suffix == ".yml"
|
|
231
|
+
|
|
232
|
+
def check(self, path: Path) -> list[Finding]:
|
|
233
|
+
findings = []
|
|
234
|
+
for i, line in enumerate(path.read_text().splitlines(), 1):
|
|
235
|
+
if "bad_pattern" in line:
|
|
236
|
+
findings.append(Finding(
|
|
237
|
+
rule_id=self.id,
|
|
238
|
+
severity=self.severity,
|
|
239
|
+
message="Found bad pattern",
|
|
240
|
+
file_path=path,
|
|
241
|
+
line=i,
|
|
242
|
+
snippet=line.strip(),
|
|
243
|
+
fix="Do this instead.",
|
|
244
|
+
))
|
|
245
|
+
return findings
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Then add it to `DEFAULT_RULES` in `src/vigil/rules/__init__.py`. Write tests. Done.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Development
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
git clone http://your-gitea/fwss/vigil.git
|
|
256
|
+
cd vigil
|
|
257
|
+
python3 -m venv venv && source venv/bin/activate
|
|
258
|
+
pip install -e ".[dev]"
|
|
259
|
+
pytest tests/ -v
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
[Business Source License 1.1](LICENSE) — free for non-commercial use. Commercial use requires a license agreement. Converts to MIT on 2030-06-26.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Feedback & Waitlist
|
|
271
|
+
|
|
272
|
+
Found a false positive? Want a rule that doesn't exist yet? Building with AI agents and hitting patterns Vigil should catch?
|
|
273
|
+
|
|
274
|
+
[Join the waitlist → thefwss.com/vigil](https://thefwss.com/vigil)
|
|
275
|
+
|
|
276
|
+
Or: `vigil feedback`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vigilsec"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI coding security co-pilot — blocks insecure code at generation time"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "BUSL-1.1"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{name = "Prem Kumar Akula", email = "prem.fwss@gmail.com"}]
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
dependencies = []
|
|
15
|
+
keywords = ["security", "devsecops", "docker", "ai", "claude", "static-analysis", "linter"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://thefwss.com/vigil"
|
|
19
|
+
Feedback = "https://thefwss.com/vigil"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
vigil = "vigil.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["src"]
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
testpaths = ["tests"]
|
|
29
|
+
pythonpath = ["src"]
|
|
30
|
+
|
|
31
|
+
[tool.bandit]
|
|
32
|
+
exclude_dirs = ["tests", ".venv", "venv"]
|
|
33
|
+
# B101: assert in tests; B404/B603/B607: vigil intentionally shells out to trivy/pip-audit/npm
|
|
34
|
+
# with hardcoded command names and list-form args — no shell injection possible
|
|
35
|
+
# B110: bare except in telemetry.py is intentional — telemetry must never crash a scan
|
|
36
|
+
skips = ["B101", "B404", "B603", "B607", "B110"]
|
vigilsec-0.1.0/setup.cfg
ADDED