websec-validator 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.
- websec_validator-0.2.0/LICENSE +21 -0
- websec_validator-0.2.0/PKG-INFO +232 -0
- websec_validator-0.2.0/README.md +220 -0
- websec_validator-0.2.0/pyproject.toml +28 -0
- websec_validator-0.2.0/setup.cfg +4 -0
- websec_validator-0.2.0/src/websec_validator/__init__.py +14 -0
- websec_validator-0.2.0/src/websec_validator/briefing.py +218 -0
- websec_validator-0.2.0/src/websec_validator/calibration.json +75 -0
- websec_validator-0.2.0/src/websec_validator/calibration.py +226 -0
- websec_validator-0.2.0/src/websec_validator/cli.py +395 -0
- websec_validator-0.2.0/src/websec_validator/constitution.py +81 -0
- websec_validator-0.2.0/src/websec_validator/corpus.json +49 -0
- websec_validator-0.2.0/src/websec_validator/dynamic.py +249 -0
- websec_validator-0.2.0/src/websec_validator/extractors/__init__.py +56 -0
- websec_validator-0.2.0/src/websec_validator/extractors/auth.py +77 -0
- websec_validator-0.2.0/src/websec_validator/extractors/authz.py +130 -0
- websec_validator-0.2.0/src/websec_validator/extractors/base.py +101 -0
- websec_validator-0.2.0/src/websec_validator/extractors/client_exposure.py +48 -0
- websec_validator-0.2.0/src/websec_validator/extractors/graphql.py +71 -0
- websec_validator-0.2.0/src/websec_validator/extractors/iac_ci.py +65 -0
- websec_validator-0.2.0/src/websec_validator/extractors/integrations.py +55 -0
- websec_validator-0.2.0/src/websec_validator/extractors/routes.py +215 -0
- websec_validator-0.2.0/src/websec_validator/extractors/schemas.py +75 -0
- websec_validator-0.2.0/src/websec_validator/extractors/stack.py +80 -0
- websec_validator-0.2.0/src/websec_validator/extractors/surface.py +86 -0
- websec_validator-0.2.0/src/websec_validator/extractors/tenant.py +33 -0
- websec_validator-0.2.0/src/websec_validator/findings.py +199 -0
- websec_validator-0.2.0/src/websec_validator/probes.py +79 -0
- websec_validator-0.2.0/src/websec_validator/proof.py +96 -0
- websec_validator-0.2.0/src/websec_validator/recon.py +28 -0
- websec_validator-0.2.0/src/websec_validator/report.py +114 -0
- websec_validator-0.2.0/src/websec_validator/scanners.py +248 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/bola-cross-tenant.sh +192 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/bola-write-verbs.py +147 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/compare-roles.sh +69 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/dlp-bypass-offline.py +149 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/hs256-brute-force.py +90 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/jwt-attacks.sh +161 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/mass-assignment.py +201 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/race-conditions.py +144 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/rate-limit-burst.sh +136 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/s3-assess.sh +120 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/ssrf-probes.sh +189 -0
- websec_validator-0.2.0/src/websec_validator/templates/probes/webhook-forgery.py +113 -0
- websec_validator-0.2.0/src/websec_validator/templates/reports/FINDINGS-SUMMARY.md.template +75 -0
- websec_validator-0.2.0/src/websec_validator/templates/reports/access-control-matrix.md.template +65 -0
- websec_validator-0.2.0/src/websec_validator/templates/reports/findings-triage.md.template +28 -0
- websec_validator-0.2.0/src/websec_validator/templates/reports/pentest-handover-brief.md.template +121 -0
- websec_validator-0.2.0/src/websec_validator/templates/reports/per-tool-FINDINGS.md.template +37 -0
- websec_validator-0.2.0/src/websec_validator.egg-info/PKG-INFO +232 -0
- websec_validator-0.2.0/src/websec_validator.egg-info/SOURCES.txt +53 -0
- websec_validator-0.2.0/src/websec_validator.egg-info/dependency_links.txt +1 -0
- websec_validator-0.2.0/src/websec_validator.egg-info/entry_points.txt +2 -0
- websec_validator-0.2.0/src/websec_validator.egg-info/top_level.txt +1 -0
- websec_validator-0.2.0/tests/test_recon.py +262 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ricardo Accioly
|
|
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,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: websec-validator
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local-first security recon that briefs your AI coding agent: facts + tailored probe scripts, code-in / artifacts-out. No LLM, no server, no running app.
|
|
5
|
+
Author: Ricardo Accioly
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: security,pentest,sast,dast,bola,ai-agent,appsec
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# websec-validator
|
|
14
|
+
|
|
15
|
+
> Local-first security recon that **briefs your AI coding agent**. It does the deterministic
|
|
16
|
+
> half — read the repo, map the full attack surface, run + de-duplicate the static scanners, and
|
|
17
|
+
> stage a probe library tailored to what it found — then hands your agent (Claude Code, Codex,
|
|
18
|
+
> Gemini, Cursor) a marching-orders briefing. **Code in, artifacts out. No LLM in the tool, no
|
|
19
|
+
> server, no running app required.**
|
|
20
|
+
|
|
21
|
+
It is *not* an autonomous scanner and *not* a SaaS. It's the missing front-half: the thing that
|
|
22
|
+
turns a repo into a precise, fact-grounded security brief an AI agent (with a human in the loop)
|
|
23
|
+
can act on — an auto-filled, repo-aware version of a senior pentester's "here's what to test and
|
|
24
|
+
how" handoff. Full landscape + why this niche is real: [`MARKET-ANALYSIS-AND-VERDICT.md`](MARKET-ANALYSIS-AND-VERDICT.md).
|
|
25
|
+
|
|
26
|
+
## Quickstart — just point it at your repo
|
|
27
|
+
|
|
28
|
+
**Simplest: tell your AI agent.** In Claude Code (or any coding agent), open your project and say:
|
|
29
|
+
|
|
30
|
+
> *"Install and run the security tool at github.com/raccioly/websec-validator on this repo, then follow its briefing."*
|
|
31
|
+
|
|
32
|
+
It installs, runs, and walks the findings with you. There's nothing to host and no website — it's
|
|
33
|
+
local. The four ways to get there, all ending in the same `AGENT-BRIEFING.md` your agent acts on:
|
|
34
|
+
|
|
35
|
+
| Path | One-time setup | Then |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| **Tell your agent** (simplest) | — | say the line above |
|
|
38
|
+
| **CLI** (a terminal) | `pipx install websec-validator` | `websec run /path/to/your/app` |
|
|
39
|
+
| **Claude Code plugin** (slash) | `/plugin marketplace add raccioly/websec-validator` → `/plugin install websec-validator@websec-plugins` | invoke the **security-pass** skill, or just ask |
|
|
40
|
+
| **Docker** (no install) | `docker build -t websec-validator .` | `docker run --rm -v "$PWD:/scan" websec-validator run /scan --out /scan/websec-out` |
|
|
41
|
+
|
|
42
|
+
➡️ **Want the reasoning behind every check?** Read **[docs/METHODOLOGY.md](docs/METHODOLOGY.md)** — what each test does and why.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pipx install websec-validator # from PyPI
|
|
48
|
+
brew install noir # OWASP Noir — the route engine (50+ frameworks); regex fallback if absent
|
|
49
|
+
websec --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
_Until the first PyPI release publishes (or for bleeding-edge), install straight from source instead:_
|
|
53
|
+
`pipx install git+https://github.com/raccioly/websec-validator` (or from a clone: `pipx install .`).
|
|
54
|
+
|
|
55
|
+
Requires **Python 3.11+** (on stock macOS, `python3` is often 3.9 — use `pipx`, which picks a newer
|
|
56
|
+
interpreter, or install via Homebrew/pyenv). Zero Python runtime dependencies: it shells out to
|
|
57
|
+
scanners (Trivy, Gitleaks, Semgrep/OpenGrep, Checkov, Prowler) and Noir **when present**, reports
|
|
58
|
+
what's missing, and never hard-fails if a tool is absent.
|
|
59
|
+
|
|
60
|
+
### Or run via Docker (everything bundled, zero install)
|
|
61
|
+
|
|
62
|
+
No need to install Noir or any scanner — the image bundles them all (arch-aware, amd64 + arm64):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
docker build -t websec-validator .
|
|
66
|
+
docker run --rm -v "$PWD:/scan" websec-validator run /scan --out /scan/websec-out
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The image carries Noir + Trivy + Gitleaks + Semgrep + Checkov; mount your repo at `/scan` and the
|
|
70
|
+
artifacts land in `/scan/websec-out`.
|
|
71
|
+
|
|
72
|
+
## Use
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
websec run ./my-app # ← the one command: recon + stage tailored probes + emit the briefing
|
|
76
|
+
websec ./my-app # same thing — a bare path defaults to `run`
|
|
77
|
+
websec run ./my-app --scan # …and also execute the available static scanners
|
|
78
|
+
websec doctor ./my-app # (optional) which scanners are installed?
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then point your agent at the output: **"Read `websec-out/AGENT-BRIEFING.md` and follow it."**
|
|
82
|
+
|
|
83
|
+
> That's the whole user surface: **`run`** (plus the optional, advanced **`dynamic`** live-probing step below). `recon`/`proof`/`calibrate` exist for developing the tool itself and are hidden from `--help` — you never need them.
|
|
84
|
+
|
|
85
|
+
## What it extracts (11 deterministic extractors, no LLM)
|
|
86
|
+
|
|
87
|
+
| | Dimension | Notable output |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| stack | languages, frameworks, datastores | monorepo-aware (aggregates every manifest) |
|
|
90
|
+
| routes | every endpoint via **OWASP Noir** | method · path · typed params · code path |
|
|
91
|
+
| auth | scheme + login surface | multi-scheme (primary jwt > passport), PyJWT/NextAuth/session aware |
|
|
92
|
+
| **authz** | access-control map | guard coverage + **write endpoints with no visible guard** + roles |
|
|
93
|
+
| tenant | multi-tenancy key candidates | the BOLA boundary, by frequency |
|
|
94
|
+
| surface | 12 user-input-gated sink classes | SSRF/SQLi/NoSQLi/traversal/SSTI/redirect/deser/XXE/proto-pollution/ReDoS/cmd/eval |
|
|
95
|
+
| schemas | data models + **privileged fields** | Pydantic/SQLAlchemy/Django/Prisma/Mongoose/TypeORM/Zod → `role`/`isAdmin`/`groupId` for mass-assignment targeting |
|
|
96
|
+
| iac_ci | IaC + CI/CD | GitHub Actions injection, unpinned actions, Dockerfile-root, tfstate |
|
|
97
|
+
| client_exposure | browser leakage | `NEXT_PUBLIC_*` secrets, server-secret-in-client, source maps |
|
|
98
|
+
| graphql | GraphQL surface | introspection / playground / missing depth-limit |
|
|
99
|
+
| integrations | third-party + webhooks | webhooks missing signature verification |
|
|
100
|
+
|
|
101
|
+
Plus **derived targeting** — IDOR / SSRF / open-redirect / upload / write / auth-endpoint
|
|
102
|
+
candidates — so probes get pointed at the *exact* endpoints, not fired blindly.
|
|
103
|
+
|
|
104
|
+
## What you get (`websec-out/`)
|
|
105
|
+
|
|
106
|
+
| Artifact | What it is |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `AGENT-BRIEFING.md` | **The product.** Marching orders: detected surface, the access-control map, targeting, findings, the method, and the staged probe list. |
|
|
109
|
+
| `FACTS.json` | The full structured recon. |
|
|
110
|
+
| `findings.json` | Static scanner results, **de-duplicated across tools** and severity-ranked (with `--scan`). |
|
|
111
|
+
| `findings-ledger.json` / `REPORT.md` | The traceable ledger: each finding with an evidence chain, CWE/ASVS/OWASP-API citation, remediation, and a **calibrated `P(real)`** (measured real-vuln rate + 95% CI + sample size). |
|
|
112
|
+
| `probes/` | The probe scripts selected + staged for *this* app (BOLA, JWT, SSRF, mass-assignment…). |
|
|
113
|
+
|
|
114
|
+
## The flow
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
🔧 websec (deterministic) 🤖 your agent + 🧑 you
|
|
118
|
+
───────────────────────────────── ─────────────────────────────────
|
|
119
|
+
1. recon → full attack surface → confirm the tenant boundary + auth model
|
|
120
|
+
2. run + de-dup static scanners → triage real-vs-noise
|
|
121
|
+
3. stage tailored probes → fill placeholders, run vs a TEST instance
|
|
122
|
+
4. emit AGENT-BRIEFING.md → propose fixes, re-run to confirm, report back
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Static recon + briefing need **only the code**. *Running* the probes needs a live test instance +
|
|
126
|
+
test credentials (the human supplies them) — the tool itself never touches a running app.
|
|
127
|
+
|
|
128
|
+
## Proof harness
|
|
129
|
+
|
|
130
|
+
`websec proof` clones a vuln-app corpus (VAmPI, NodeGoat, DVGA) and scores whether recon surfaces
|
|
131
|
+
each app's documented attack surface — a deterministic, CI-trackable proxy (currently **10/10**).
|
|
132
|
+
The real kill-criterion (does the briefing lift an agent's bug-finding vs a generic prompt?) is the
|
|
133
|
+
manual A/B in [`corpus/PROOF-PROTOCOL.md`](corpus/PROOF-PROTOCOL.md).
|
|
134
|
+
|
|
135
|
+
## Calibrated confidence
|
|
136
|
+
|
|
137
|
+
`websec calibrate` runs the ledger against the labeled corpus, measures how often each
|
|
138
|
+
*(attack-class, confidence)* bucket is a **real** documented vuln, and writes `calibration.json`
|
|
139
|
+
(shipped + applied at runtime). Each finding then carries `P(real)` with a **95% Wilson confidence
|
|
140
|
+
interval** and the sample size `n` — so "MEDIUM" stops being a vibe and becomes "real ~57% of the
|
|
141
|
+
time on the corpus (CI 43–70%, n=51)". A finding that matches no documented vuln counts as a false
|
|
142
|
+
positive (the corpus is well-documented). **Honest caveats:** the corpus is *deliberately
|
|
143
|
+
vulnerable*, so the rates skew **optimistic** for clean production code, and small samples mean
|
|
144
|
+
**wide intervals** — the CI is the headline, not the point estimate, and both tighten as the corpus
|
|
145
|
+
grows. With thin data a bucket falls back to the per-label aggregate, then to a clearly-flagged
|
|
146
|
+
uncalibrated prior. No ML, no deps — binomial proportion + Wilson interval; the structure upgrades to
|
|
147
|
+
isotonic regression if a large labeled set ever exists.
|
|
148
|
+
|
|
149
|
+
**It self-improves.** `websec dynamic` is an *oracle*: a write that executes unauthenticated is a
|
|
150
|
+
confirmed real vuln, and a recon-flagged endpoint that turns out auth-enforced is a confirmed false
|
|
151
|
+
positive. Every dynamic run folds those confirmed labels into a **local overlay** (`~/.cache/websec-validator/`,
|
|
152
|
+
gitignored, never shipped) that's merged on top of the public table — so the numbers **personalize to
|
|
153
|
+
your apps** the more you run it, with no extra step and nothing leaving your machine. To label by hand
|
|
154
|
+
instead, feed a `{attack_class, confidence, is_real}` file to `websec calibrate --ingest`.
|
|
155
|
+
|
|
156
|
+
## Dynamic phase (v2 — read-only so far)
|
|
157
|
+
|
|
158
|
+
When you have a *running TEST instance*, `websec dynamic` mints role tokens and runs the probes the
|
|
159
|
+
static recon pointed at. v1 is **read-only**: authenticated **cross-tenant BOLA** on the group-scoped
|
|
160
|
+
GET endpoints recon discovered.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
cp dynamic-config.example.json dynamic-config.json # TEST target + role creds (gitignored)
|
|
164
|
+
websec run ./my-app # static recon → websec-out/FACTS.json
|
|
165
|
+
websec dynamic --config dynamic-config.json --facts websec-out/FACTS.json
|
|
166
|
+
# → "14/14 cross-tenant GET reads blocked — all isolated" (or 🚨 LEAK with the exact endpoint)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Never point it at production. Write-verb BOLA, JWT/auth attacks, and a ZAP/Nuclei two-role diff are
|
|
170
|
+
the next dynamic probes (explicitly gated — they mutate).
|
|
171
|
+
|
|
172
|
+
## Validated on
|
|
173
|
+
|
|
174
|
+
HugoCross (Next.js), `wu-whatsappinbox` (106-service Express/AWS monorepo), VAmPI, NodeGoat, DVGA —
|
|
175
|
+
independently reproducing a hand-done pentest's findings (tenant boundary, SSO-endpoint SSRF, media
|
|
176
|
+
upload, conversation-BOLA routes, roles).
|
|
177
|
+
|
|
178
|
+
## Tests
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
python3 -m unittest discover -s tests # stdlib only, no Noir/network — 23 tests
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Releasing (maintainer)
|
|
185
|
+
|
|
186
|
+
Published to PyPI via **Trusted Publishing** (OIDC — no API token in the repo). To cut a release:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# 1. bump the version in pyproject.toml (e.g. 0.2.0 → 0.2.1)
|
|
190
|
+
# 2. tag it and push — the tag must match pyproject's version (CI verifies):
|
|
191
|
+
git tag v0.2.1 && git push origin v0.2.1
|
|
192
|
+
# → .github/workflows/publish.yml builds + publishes to PyPI
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
One-time PyPI setup (before the first release): on pypi.org → **Account → Publishing → Add a pending
|
|
196
|
+
publisher** with project `websec-validator`, owner `raccioly`, repo `websec-validator`, workflow
|
|
197
|
+
`publish.yml`, environment `pypi`. The project is created on the first successful publish.
|
|
198
|
+
|
|
199
|
+
> Two independent channels, two update mechanisms: the **CLI** ships to **PyPI** (semver releases,
|
|
200
|
+
> `pip install --upgrade`); the **Claude Code plugin** ships from **git** (tracks latest commit,
|
|
201
|
+
> refreshed via `/plugin marketplace update`).
|
|
202
|
+
|
|
203
|
+
## Status / roadmap
|
|
204
|
+
|
|
205
|
+
**Done:** 11-extractor recon (incl. schema/entity → mass-assignment targeting), cross-tool de-dup,
|
|
206
|
+
tailored probe staging, agent briefing, traceable findings ledger with **calibrated confidence
|
|
207
|
+
(CJE — Wilson CIs)**, proof harness, test suite, **Docker bundle** (all scanners + Noir, arch-aware),
|
|
208
|
+
**dynamic phase v1** (authenticated read-only cross-tenant BOLA — validated live, reproduced a
|
|
209
|
+
hand-pentest's 14/14).
|
|
210
|
+
**Next:** dynamic write-verb BOLA + JWT/auth probes + ZAP/Nuclei two-role diff (gated, they mutate),
|
|
211
|
+
calibration on hand-labeled real repos (more representative base rate), ASVS index lookup, optional
|
|
212
|
+
model-SDK adapters for no-agent fallback.
|
|
213
|
+
|
|
214
|
+
## Using it as a Claude Code skill / plugin
|
|
215
|
+
|
|
216
|
+
This repo **is** a Claude Code plugin. Install it once —
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
/plugin marketplace add raccioly/websec-validator
|
|
220
|
+
/plugin install websec-validator@websec-plugins
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
— and the bundled **security-pass** skill ([`skills/security-pass/SKILL.md`](skills/security-pass/SKILL.md))
|
|
224
|
+
lets you just ask, in plain English, for a security pass: it runs `websec`, reads the briefing, and
|
|
225
|
+
works the findings with you. For other agents the universal interface is unchanged: run the CLI, read
|
|
226
|
+
`AGENT-BRIEFING.md`.
|
|
227
|
+
|
|
228
|
+
## Credits
|
|
229
|
+
|
|
230
|
+
Methodology + probe library come from a real authenticated pentest pass
|
|
231
|
+
([`base-research/REPLICATION-PLAYBOOK.md`](base-research/REPLICATION-PLAYBOOK.md), not committed).
|
|
232
|
+
This tool productizes that hand-written pass into something an AI agent can run on any repo.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# websec-validator
|
|
2
|
+
|
|
3
|
+
> Local-first security recon that **briefs your AI coding agent**. It does the deterministic
|
|
4
|
+
> half — read the repo, map the full attack surface, run + de-duplicate the static scanners, and
|
|
5
|
+
> stage a probe library tailored to what it found — then hands your agent (Claude Code, Codex,
|
|
6
|
+
> Gemini, Cursor) a marching-orders briefing. **Code in, artifacts out. No LLM in the tool, no
|
|
7
|
+
> server, no running app required.**
|
|
8
|
+
|
|
9
|
+
It is *not* an autonomous scanner and *not* a SaaS. It's the missing front-half: the thing that
|
|
10
|
+
turns a repo into a precise, fact-grounded security brief an AI agent (with a human in the loop)
|
|
11
|
+
can act on — an auto-filled, repo-aware version of a senior pentester's "here's what to test and
|
|
12
|
+
how" handoff. Full landscape + why this niche is real: [`MARKET-ANALYSIS-AND-VERDICT.md`](MARKET-ANALYSIS-AND-VERDICT.md).
|
|
13
|
+
|
|
14
|
+
## Quickstart — just point it at your repo
|
|
15
|
+
|
|
16
|
+
**Simplest: tell your AI agent.** In Claude Code (or any coding agent), open your project and say:
|
|
17
|
+
|
|
18
|
+
> *"Install and run the security tool at github.com/raccioly/websec-validator on this repo, then follow its briefing."*
|
|
19
|
+
|
|
20
|
+
It installs, runs, and walks the findings with you. There's nothing to host and no website — it's
|
|
21
|
+
local. The four ways to get there, all ending in the same `AGENT-BRIEFING.md` your agent acts on:
|
|
22
|
+
|
|
23
|
+
| Path | One-time setup | Then |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| **Tell your agent** (simplest) | — | say the line above |
|
|
26
|
+
| **CLI** (a terminal) | `pipx install websec-validator` | `websec run /path/to/your/app` |
|
|
27
|
+
| **Claude Code plugin** (slash) | `/plugin marketplace add raccioly/websec-validator` → `/plugin install websec-validator@websec-plugins` | invoke the **security-pass** skill, or just ask |
|
|
28
|
+
| **Docker** (no install) | `docker build -t websec-validator .` | `docker run --rm -v "$PWD:/scan" websec-validator run /scan --out /scan/websec-out` |
|
|
29
|
+
|
|
30
|
+
➡️ **Want the reasoning behind every check?** Read **[docs/METHODOLOGY.md](docs/METHODOLOGY.md)** — what each test does and why.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pipx install websec-validator # from PyPI
|
|
36
|
+
brew install noir # OWASP Noir — the route engine (50+ frameworks); regex fallback if absent
|
|
37
|
+
websec --version
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
_Until the first PyPI release publishes (or for bleeding-edge), install straight from source instead:_
|
|
41
|
+
`pipx install git+https://github.com/raccioly/websec-validator` (or from a clone: `pipx install .`).
|
|
42
|
+
|
|
43
|
+
Requires **Python 3.11+** (on stock macOS, `python3` is often 3.9 — use `pipx`, which picks a newer
|
|
44
|
+
interpreter, or install via Homebrew/pyenv). Zero Python runtime dependencies: it shells out to
|
|
45
|
+
scanners (Trivy, Gitleaks, Semgrep/OpenGrep, Checkov, Prowler) and Noir **when present**, reports
|
|
46
|
+
what's missing, and never hard-fails if a tool is absent.
|
|
47
|
+
|
|
48
|
+
### Or run via Docker (everything bundled, zero install)
|
|
49
|
+
|
|
50
|
+
No need to install Noir or any scanner — the image bundles them all (arch-aware, amd64 + arm64):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
docker build -t websec-validator .
|
|
54
|
+
docker run --rm -v "$PWD:/scan" websec-validator run /scan --out /scan/websec-out
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The image carries Noir + Trivy + Gitleaks + Semgrep + Checkov; mount your repo at `/scan` and the
|
|
58
|
+
artifacts land in `/scan/websec-out`.
|
|
59
|
+
|
|
60
|
+
## Use
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
websec run ./my-app # ← the one command: recon + stage tailored probes + emit the briefing
|
|
64
|
+
websec ./my-app # same thing — a bare path defaults to `run`
|
|
65
|
+
websec run ./my-app --scan # …and also execute the available static scanners
|
|
66
|
+
websec doctor ./my-app # (optional) which scanners are installed?
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then point your agent at the output: **"Read `websec-out/AGENT-BRIEFING.md` and follow it."**
|
|
70
|
+
|
|
71
|
+
> That's the whole user surface: **`run`** (plus the optional, advanced **`dynamic`** live-probing step below). `recon`/`proof`/`calibrate` exist for developing the tool itself and are hidden from `--help` — you never need them.
|
|
72
|
+
|
|
73
|
+
## What it extracts (11 deterministic extractors, no LLM)
|
|
74
|
+
|
|
75
|
+
| | Dimension | Notable output |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| stack | languages, frameworks, datastores | monorepo-aware (aggregates every manifest) |
|
|
78
|
+
| routes | every endpoint via **OWASP Noir** | method · path · typed params · code path |
|
|
79
|
+
| auth | scheme + login surface | multi-scheme (primary jwt > passport), PyJWT/NextAuth/session aware |
|
|
80
|
+
| **authz** | access-control map | guard coverage + **write endpoints with no visible guard** + roles |
|
|
81
|
+
| tenant | multi-tenancy key candidates | the BOLA boundary, by frequency |
|
|
82
|
+
| surface | 12 user-input-gated sink classes | SSRF/SQLi/NoSQLi/traversal/SSTI/redirect/deser/XXE/proto-pollution/ReDoS/cmd/eval |
|
|
83
|
+
| schemas | data models + **privileged fields** | Pydantic/SQLAlchemy/Django/Prisma/Mongoose/TypeORM/Zod → `role`/`isAdmin`/`groupId` for mass-assignment targeting |
|
|
84
|
+
| iac_ci | IaC + CI/CD | GitHub Actions injection, unpinned actions, Dockerfile-root, tfstate |
|
|
85
|
+
| client_exposure | browser leakage | `NEXT_PUBLIC_*` secrets, server-secret-in-client, source maps |
|
|
86
|
+
| graphql | GraphQL surface | introspection / playground / missing depth-limit |
|
|
87
|
+
| integrations | third-party + webhooks | webhooks missing signature verification |
|
|
88
|
+
|
|
89
|
+
Plus **derived targeting** — IDOR / SSRF / open-redirect / upload / write / auth-endpoint
|
|
90
|
+
candidates — so probes get pointed at the *exact* endpoints, not fired blindly.
|
|
91
|
+
|
|
92
|
+
## What you get (`websec-out/`)
|
|
93
|
+
|
|
94
|
+
| Artifact | What it is |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `AGENT-BRIEFING.md` | **The product.** Marching orders: detected surface, the access-control map, targeting, findings, the method, and the staged probe list. |
|
|
97
|
+
| `FACTS.json` | The full structured recon. |
|
|
98
|
+
| `findings.json` | Static scanner results, **de-duplicated across tools** and severity-ranked (with `--scan`). |
|
|
99
|
+
| `findings-ledger.json` / `REPORT.md` | The traceable ledger: each finding with an evidence chain, CWE/ASVS/OWASP-API citation, remediation, and a **calibrated `P(real)`** (measured real-vuln rate + 95% CI + sample size). |
|
|
100
|
+
| `probes/` | The probe scripts selected + staged for *this* app (BOLA, JWT, SSRF, mass-assignment…). |
|
|
101
|
+
|
|
102
|
+
## The flow
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
🔧 websec (deterministic) 🤖 your agent + 🧑 you
|
|
106
|
+
───────────────────────────────── ─────────────────────────────────
|
|
107
|
+
1. recon → full attack surface → confirm the tenant boundary + auth model
|
|
108
|
+
2. run + de-dup static scanners → triage real-vs-noise
|
|
109
|
+
3. stage tailored probes → fill placeholders, run vs a TEST instance
|
|
110
|
+
4. emit AGENT-BRIEFING.md → propose fixes, re-run to confirm, report back
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Static recon + briefing need **only the code**. *Running* the probes needs a live test instance +
|
|
114
|
+
test credentials (the human supplies them) — the tool itself never touches a running app.
|
|
115
|
+
|
|
116
|
+
## Proof harness
|
|
117
|
+
|
|
118
|
+
`websec proof` clones a vuln-app corpus (VAmPI, NodeGoat, DVGA) and scores whether recon surfaces
|
|
119
|
+
each app's documented attack surface — a deterministic, CI-trackable proxy (currently **10/10**).
|
|
120
|
+
The real kill-criterion (does the briefing lift an agent's bug-finding vs a generic prompt?) is the
|
|
121
|
+
manual A/B in [`corpus/PROOF-PROTOCOL.md`](corpus/PROOF-PROTOCOL.md).
|
|
122
|
+
|
|
123
|
+
## Calibrated confidence
|
|
124
|
+
|
|
125
|
+
`websec calibrate` runs the ledger against the labeled corpus, measures how often each
|
|
126
|
+
*(attack-class, confidence)* bucket is a **real** documented vuln, and writes `calibration.json`
|
|
127
|
+
(shipped + applied at runtime). Each finding then carries `P(real)` with a **95% Wilson confidence
|
|
128
|
+
interval** and the sample size `n` — so "MEDIUM" stops being a vibe and becomes "real ~57% of the
|
|
129
|
+
time on the corpus (CI 43–70%, n=51)". A finding that matches no documented vuln counts as a false
|
|
130
|
+
positive (the corpus is well-documented). **Honest caveats:** the corpus is *deliberately
|
|
131
|
+
vulnerable*, so the rates skew **optimistic** for clean production code, and small samples mean
|
|
132
|
+
**wide intervals** — the CI is the headline, not the point estimate, and both tighten as the corpus
|
|
133
|
+
grows. With thin data a bucket falls back to the per-label aggregate, then to a clearly-flagged
|
|
134
|
+
uncalibrated prior. No ML, no deps — binomial proportion + Wilson interval; the structure upgrades to
|
|
135
|
+
isotonic regression if a large labeled set ever exists.
|
|
136
|
+
|
|
137
|
+
**It self-improves.** `websec dynamic` is an *oracle*: a write that executes unauthenticated is a
|
|
138
|
+
confirmed real vuln, and a recon-flagged endpoint that turns out auth-enforced is a confirmed false
|
|
139
|
+
positive. Every dynamic run folds those confirmed labels into a **local overlay** (`~/.cache/websec-validator/`,
|
|
140
|
+
gitignored, never shipped) that's merged on top of the public table — so the numbers **personalize to
|
|
141
|
+
your apps** the more you run it, with no extra step and nothing leaving your machine. To label by hand
|
|
142
|
+
instead, feed a `{attack_class, confidence, is_real}` file to `websec calibrate --ingest`.
|
|
143
|
+
|
|
144
|
+
## Dynamic phase (v2 — read-only so far)
|
|
145
|
+
|
|
146
|
+
When you have a *running TEST instance*, `websec dynamic` mints role tokens and runs the probes the
|
|
147
|
+
static recon pointed at. v1 is **read-only**: authenticated **cross-tenant BOLA** on the group-scoped
|
|
148
|
+
GET endpoints recon discovered.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cp dynamic-config.example.json dynamic-config.json # TEST target + role creds (gitignored)
|
|
152
|
+
websec run ./my-app # static recon → websec-out/FACTS.json
|
|
153
|
+
websec dynamic --config dynamic-config.json --facts websec-out/FACTS.json
|
|
154
|
+
# → "14/14 cross-tenant GET reads blocked — all isolated" (or 🚨 LEAK with the exact endpoint)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Never point it at production. Write-verb BOLA, JWT/auth attacks, and a ZAP/Nuclei two-role diff are
|
|
158
|
+
the next dynamic probes (explicitly gated — they mutate).
|
|
159
|
+
|
|
160
|
+
## Validated on
|
|
161
|
+
|
|
162
|
+
HugoCross (Next.js), `wu-whatsappinbox` (106-service Express/AWS monorepo), VAmPI, NodeGoat, DVGA —
|
|
163
|
+
independently reproducing a hand-done pentest's findings (tenant boundary, SSO-endpoint SSRF, media
|
|
164
|
+
upload, conversation-BOLA routes, roles).
|
|
165
|
+
|
|
166
|
+
## Tests
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python3 -m unittest discover -s tests # stdlib only, no Noir/network — 23 tests
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Releasing (maintainer)
|
|
173
|
+
|
|
174
|
+
Published to PyPI via **Trusted Publishing** (OIDC — no API token in the repo). To cut a release:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# 1. bump the version in pyproject.toml (e.g. 0.2.0 → 0.2.1)
|
|
178
|
+
# 2. tag it and push — the tag must match pyproject's version (CI verifies):
|
|
179
|
+
git tag v0.2.1 && git push origin v0.2.1
|
|
180
|
+
# → .github/workflows/publish.yml builds + publishes to PyPI
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
One-time PyPI setup (before the first release): on pypi.org → **Account → Publishing → Add a pending
|
|
184
|
+
publisher** with project `websec-validator`, owner `raccioly`, repo `websec-validator`, workflow
|
|
185
|
+
`publish.yml`, environment `pypi`. The project is created on the first successful publish.
|
|
186
|
+
|
|
187
|
+
> Two independent channels, two update mechanisms: the **CLI** ships to **PyPI** (semver releases,
|
|
188
|
+
> `pip install --upgrade`); the **Claude Code plugin** ships from **git** (tracks latest commit,
|
|
189
|
+
> refreshed via `/plugin marketplace update`).
|
|
190
|
+
|
|
191
|
+
## Status / roadmap
|
|
192
|
+
|
|
193
|
+
**Done:** 11-extractor recon (incl. schema/entity → mass-assignment targeting), cross-tool de-dup,
|
|
194
|
+
tailored probe staging, agent briefing, traceable findings ledger with **calibrated confidence
|
|
195
|
+
(CJE — Wilson CIs)**, proof harness, test suite, **Docker bundle** (all scanners + Noir, arch-aware),
|
|
196
|
+
**dynamic phase v1** (authenticated read-only cross-tenant BOLA — validated live, reproduced a
|
|
197
|
+
hand-pentest's 14/14).
|
|
198
|
+
**Next:** dynamic write-verb BOLA + JWT/auth probes + ZAP/Nuclei two-role diff (gated, they mutate),
|
|
199
|
+
calibration on hand-labeled real repos (more representative base rate), ASVS index lookup, optional
|
|
200
|
+
model-SDK adapters for no-agent fallback.
|
|
201
|
+
|
|
202
|
+
## Using it as a Claude Code skill / plugin
|
|
203
|
+
|
|
204
|
+
This repo **is** a Claude Code plugin. Install it once —
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
/plugin marketplace add raccioly/websec-validator
|
|
208
|
+
/plugin install websec-validator@websec-plugins
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
— and the bundled **security-pass** skill ([`skills/security-pass/SKILL.md`](skills/security-pass/SKILL.md))
|
|
212
|
+
lets you just ask, in plain English, for a security pass: it runs `websec`, reads the briefing, and
|
|
213
|
+
works the findings with you. For other agents the universal interface is unchanged: run the CLI, read
|
|
214
|
+
`AGENT-BRIEFING.md`.
|
|
215
|
+
|
|
216
|
+
## Credits
|
|
217
|
+
|
|
218
|
+
Methodology + probe library come from a real authenticated pentest pass
|
|
219
|
+
([`base-research/REPLICATION-PLAYBOOK.md`](base-research/REPLICATION-PLAYBOOK.md), not committed).
|
|
220
|
+
This tool productizes that hand-written pass into something an AI agent can run on any repo.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "websec-validator"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Local-first security recon that briefs your AI coding agent: facts + tailored probe scripts, code-in / artifacts-out. No LLM, no server, no running app."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Ricardo Accioly" }]
|
|
13
|
+
keywords = ["security", "pentest", "sast", "dast", "bola", "ai-agent", "appsec"]
|
|
14
|
+
# v1 deliberately has ZERO runtime dependencies — stdlib only.
|
|
15
|
+
# It SHELLS OUT to scanners (trivy/gitleaks/semgrep/...) when present; it does not import them.
|
|
16
|
+
dependencies = []
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
websec = "websec_validator.cli:main"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
package-dir = { "" = "src" }
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["src"]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.package-data]
|
|
28
|
+
websec_validator = ["templates/probes/*", "templates/reports/*", "corpus.json", "calibration.json"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""websec-validator — local-first security recon that briefs an AI coding agent.
|
|
2
|
+
|
|
3
|
+
The tool does the deterministic half (read the repo, run the scanners it finds,
|
|
4
|
+
stage the probe library tailored to what it discovered) and emits three artifacts:
|
|
5
|
+
|
|
6
|
+
1. findings.json — de-duplicated static scanner results
|
|
7
|
+
2. FACTS.json — stack, routes, auth-model candidates, attack surface
|
|
8
|
+
3. AGENT-BRIEFING.md — marching orders + staged probe scripts for your AI agent
|
|
9
|
+
|
|
10
|
+
It never calls an LLM, never runs a server, and never needs a running instance of
|
|
11
|
+
the target app. Running the probes and applying fixes is the agent + human's job.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|