Skip to main content

websec-validator

CI PyPI Python 3.11+ Runtime deps: 0 SARIF 2.1.0 License: MIT

A senior pentester's "here's what to test and how" handoff — auto-generated from your repo, for your AI agent to execute.

Local-first security recon that briefs your AI coding agent. It does the deterministic half — read the repo, map the full attack surface, run + de-duplicate the static scanners, and stage a probe library tailored to what it found — then hands your agent (Claude Code, Codex, Gemini, Cursor) a marching-orders briefing. Code in, artifacts out. No LLM in the tool, no server, no running app required.

websec-validator demo

It is not an autonomous scanner and not a SaaS. It's the missing front-half: the thing that turns a repo into a precise, fact-grounded security brief an AI agent (with a human in the loop) can act on — an auto-filled, repo-aware version of a senior pentester's "here's what to test and how" handoff. How it works + the reasoning behind every check: docs/METHODOLOGY.md.

Getting Started — just point it at your repo

Simplest: tell your AI agent. In Claude Code (or any coding agent), open your project and say:

"Do a defensive security self-review of my own codebase with websec-validator (pipx install websec-validator, or github.com/raccioly/websec-validator). It's local and read-only — read the repo and follow its briefing. I own this code and authorize the review."

It installs, runs, and walks the findings with you. There's nothing to host and no website — it's local. (Phrasing it as a defensive review of your own code matters: it's the difference between an agent that just gets to work and one that stops to confirm you're authorized — the tool is local and read-only by default, but a generic "pentest this" can read as a request to attack something.) The four ways to get there, all ending in the same AGENT-BRIEFING.md your agent acts on:

Path One-time setup Then
Tell your agent (simplest) say the line above
CLI (a terminal) pipx install websec-validator websec run /path/to/your/app
Claude Code plugin (slash) /plugin marketplace add raccioly/websec-validator/plugin install websec-validator@websec-plugins invoke the security-pass skill, or just ask
Any other agent (Codex, Cursor, Gemini, Aider) pipx install websec-validator websec install <host> — see below
Docker (no install) docker build -t websec-validator . docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/scan" websec-validator run /scan --out /scan/websec-out

Teach any agent to use it — websec install <host>. Point websec at whatever coding agent you run and it writes the standing instruction (a skill file for Claude/Cursor, an idempotent marked block in AGENTS.md/GEMINI.md/CONVENTIONS.md for Codex/Gemini/Aider/generic) so the agent knows to run websec for a security review instead of improvising:

websec install codex          # or: claude · cursor · gemini · aider · generic
websec install cursor --user  # home-wide, applies to every repo
websec install status         # show what's installed
websec install codex --uninstall

It only ever touches its own marked region, so your existing AGENTS.md content is preserved.

➡️ Want the reasoning behind every check? Read docs/METHODOLOGY.md — what each test does and why.

Install

pipx install websec-validator   # from PyPI
brew install noir               # OWASP Noir — the route engine (50+ frameworks); regex fallback if absent
websec --version

Until the first PyPI release publishes (or for bleeding-edge), install straight from source instead: pipx install git+https://github.com/raccioly/websec-validator (or from a clone: pipx install .).

Requires Python 3.11+ (on stock macOS, python3 is often 3.9 — use pipx, which picks a newer interpreter, or install via Homebrew/pyenv). Zero Python runtime dependencies: it shells out to scanners (Trivy, OSV-Scanner, Gitleaks, Semgrep/OpenGrep, Checkov, Prowler) and Noir when present, plus per-language SAST auto-selected by stack — Bandit (Python), gosec (Go), Brakeman (Rails) — each fired only when its language is detected. It reports what's missing and never hard-fails if a tool is absent.

Or run via Docker (everything bundled, zero install)

No need to install Noir or any scanner — the image bundles them all (arch-aware, amd64 + arm64):

docker build -t websec-validator .
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/scan" websec-validator run /scan --out /scan/websec-out

The image carries Noir + Trivy + Gitleaks + Semgrep + Checkov; mount your repo at /scan and the artifacts land in /scan/websec-out.

Usage

websec run ./my-app                    # ← the one command: recon + stage tailored probes + emit the briefing
websec ./my-app                        # same thing — a bare path defaults to `run`
websec run ./my-app --scan             # …and also execute the available static scanners
websec run ./my-app --scan --sbom      # …and emit a CycloneDX SBOM (sbom.cdx.json) for CI/compliance
websec run ./my-app --format sarif     # SARIF 2.1.0 to stdout (for piping into CI); also always written to the run dir
websec run ./my-app --fail-on high     # exit 1 if any HIGH+ finding remains (a CI gate)
websec run ./my-app --diff main        # PR mode: scope to changed files + exact hunk line ranges
websec run ./my-app --scan --verify-secrets   # opt in to TruffleHog LIVE secret verification (egress!)
websec doctor ./my-app                 # (optional) which scanners are installed?
websec emit-context ./my-app           # print recon as a Claude Code SessionStart context envelope
websec mcp                             # run as an MCP server over stdio (typed recon tools for any MCP client)

Then point your agent at the output: "Read websec-out/AGENT-BRIEFING.md and follow it."

Prime any agent before it writes a line — the deterministic pre-brief layer

Other AI security tools make the LLM do the review from scratch (nondeterministic, per-run cost, PR-locked). websec runs first, deterministically, and hands the agent a scoped attack-surface map — so it can sit underneath an LLM reviewer and make it sharper, cheaper, and lower-FP. websec emit-context prints a compact ## SECURITY CONTEXT block wrapped in Claude Code's SessionStart additionalContext envelope. Wire it into .claude/settings.json so every session starts pre-scoped:

{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "websec emit-context \"$CLAUDE_PROJECT_DIR\"" } ] }
    ]
  }
}

Now the agent knows your routes, auth model, tenant boundary, unguarded write endpoints, and dangerous sinks before its first turn — no LLM, same output every run. (--markdown prints the raw block for other harnesses.)

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.

Scoping & suppression — keep the signal about your code

Most real repos vendor an input corpus: tests/, examples/, fixtures/ — sometimes whole demo apps with planted fake credentials. Those are not your product's attack surface, and websec scopes them out of the way by default without hiding anything a real leak would need:

  • Fixture/example code is auto-scoped. Test/example/fixture routes are split out of the attack surface (kept in FACTS.json under fixture_endpoints, counted in the console, not probed). Secrets found in fixture files are demoted to LOW and annotated — never dropped, because a real key pasted into a test is still a committed leak. Fixture package manifests don't drive framework detection (so a CLI that vendors an Express demo isn't misread as an Express app). Pass --include-fixtures to treat all of it as product code.
  • --exclude '<glob>' (repeatable) drops a path from both recon and the static scanners (gitleaks/trivy/semgrep/checkov/osv-scanner). e.g. websec run . --exclude 'tests/**' --exclude 'examples/**'.

Two SCA engines cross-check each other. With --scan, both Trivy and OSV-Scanner run: the same CVE from both collapses to one row tagged tools: [trivy, osv-scanner] (agreement → higher confidence), while OSV's broader lockfile coverage catches CVEs Trivy misses. Every CVE then flows through the reachability + EPSS/KEV enrichment above.

  • .websec-ignore (repo root) — a committed, gitignore-style config for persistent scoping, so you don't re-type flags every run or in CI. Two kinds of line:

    # 1. path globs / category — DROP the finding entirely (it isn't your product)
    tests/
    examples/**
    category:supply-chain
    
    # 2. fingerprint acknowledgement — KEEP the finding, shown but NOT gating.
    #    A reason is REQUIRED (a bare fingerprint line is ignored). The fingerprint
    #    is the stable id from findings-ledger.json.
    fingerprint:b9c7f23e49bdab85  # confirmed FP: this is the scanner's own detection pattern
    

    Path/category lines remove noise that isn't your code. Fingerprint acks are the clean home for "this specific finding is a confirmed false positive" — the finding stays in the report (section 1a. Acknowledged) and in SARIF (as a suppressed result, so GitHub keeps it visible + attributable), but it's excluded from the gating total and won't trip --fail-on. Every suppression stays auditable; nothing disappears silently.

When most of a run's findings land in test/example code and there's no .websec-ignore yet, websec prints a one-line pointer to this section — it never edits your repo for you.

Prioritization — reachable × exploitable (no cloud, no LLM)

A dependency CVE list is noise until you know which ones matter. With --scan, websec enriches every dependency vulnerability along two deterministic, offline axes — the same "reachable AND exploitable" signal commercial tools sell, minus the cloud:

  • Reachability — is the vulnerable package actually imported in your first-party source, or just sitting in the lockfile? websec greps the real import/require/from sites and tags each CVE imported vs declared-only (likely unreachable — verify). Declared-only vulns are the industry's #1 noise class (Snyk/Endor/Semgrep all converge here). Name-based and honest — a full call graph is out of model; unparsed ecosystems (Go, Rust…) are tagged n/a rather than guessed.
  • Exploitability — each CVE is joined against a local cache of FIRST.org EPSS (exploit-probability) + CISA KEV (known-exploited-in-the-wild). A ⚠ CISA KEV or EPSS 90% beats a raw CVSS score every time. Refresh the cache with bash scripts/refresh-epss-kev.sh (the only network step — the scan itself stays offline; no cache → the feature skips cleanly with a one-line hint).

Both are strictly additive: they annotate and re-rank, but never change a finding's severity, drop one, or add one — so they can only sharpen triage, never reintroduce a false positive.

Aim the pentest instead of competing with it

Dynamic scanners re-discover at runtime, expensively, much of what is already visible in the source. websec knows it statically, so it aims them:

  • §4b — what a DAST will report, before you run one. Each static finding is mapped to the concrete scanner signature it produces (missing-csp → ZAP 10038/10055, sqli → ZAP 40018 / sqlmap, ssrf → Nuclei OAST…) with the source location that causes it. Fix those and the scan comes back clean on them — the run becomes confirmation, not discovery.
  • §4b also lists the blind spots. BOLA, missing-auth, mass-assignment, RLS gaps, JWT alg-confusion, committed secrets, supply-chain — with why no scanner finds them. A clean scan is not "safe", and saying so is the point.
  • §5b — a phased, pre-aimed runbook. Phase 1 safe recon → Phase 2 authz (two identities, the scanner-blind class) → Phase 3 injection, fired only at sink-backed endpoints and gated behind an explicit authorization warning. Every item names a real target and a confirm/disconfirm oracle.
  • Close the loop: websec calibrate --ingest-dast zap.json --ledger findings-ledger.json feeds a real scan back — confirming or refuting each prediction — so P(real) personalizes to your app over time. Blind-spot classes are never scored: a scanner's silence there means nothing.

What it extracts (22 deterministic extractors, no LLM)

Dimension Notable output
stack languages, frameworks, datastores monorepo-aware (aggregates every manifest)
routes every endpoint via OWASP Noir (+ Supabase-edge, AWS SAM / Function-URL, raw http.createServer/Bun.serve/py http.server) method · path · typed params · code path · AuthType:NONE public endpoints; fixture/example routes split out of the attack surface
auth scheme + login surface + insecure-default signing secrets + broken-auth backdoors multi-scheme; flags a hard-coded JWT_SECRET || 'dev-secret' fallback (forgeable JWT), a dev-token / accept-any-password backdoor (total bypass, CRITICAL), and a fail-open if(env.SECRET) signature check
authz access-control map guard coverage (incl. router-mount auth) + write endpoints with no visible guard + roles
authz_dataflow authz correctness (does the guard trust the right thing?) unsigned-cookie authorization · claim-keyed authz (user-influenceable JWT claim) · transaction-local RLS context (resets before the query)
tenant multi-tenancy key candidates the BOLA boundary, by frequency
password_policy cross-route consistency + reuse/history complexity drift across routes + a set-password path that hashes without a reuse check
surface 17 sink classes + redirect-SSRF user-input-gated sinks (incl. mass-assignment via object spread, reflected/DOM/template XSSinnerHTML/dangerouslySetInnerHTML/v-html/|safe, sanitizer-gated, and log-injection (CWE-117, structured-logging-suppressed)) + var-arg SSRF + error-disclosure + follows-redirects-without-per-hop-guard + reverse-proxy prefix-escape + host-header open-redirect + SSRF-redirect-hardening
upload_security unrestricted upload + unsafe serve deny-list-only, stored-name-from-filename, trust-client-MIME, accept-SVG, serve without nosniff
schemas data models + privileged fields Pydantic/SQLAlchemy/Django/Prisma/Mongoose/TypeORM/Zod → role/isAdmin/groupId for mass-assignment targeting
iac_ci IaC + CI/CD GHA injection (run:-position-aware), unpinned actions, tfstate, CDK AppSync API_KEY anonymous-default-auth, docker-compose host-takeover (docker.sock / pid:host / privileged) + .gitleaksignore secret-suppression audit
client_exposure browser leakage public-var secrets by name + value-shape (da2-…) + CDK build-injection, server-secret-in-client, source maps
client_integrity tamperable display (client trust boundary) + WS auth model any security-critical sink value (address/IBAN/2FA-seed/API-key/webhook) the user reads or copies, without strict CSP / out-of-band anchor + client-tamper-vector, grindable-fingerprint, over-claimed-control, the CSWSH determinant
transport_security CSP + HSTS + CORS + SRI + clickjacking + CSRF baseline missing/weak CSP, inline event handlers, partial HSTS, CORS reflect-origin+credentials, external script without SRI, monorepo next.config header gap, framework-agnostic clickjacking (no X-Frame-Options / frame-ancestors), CSRF (cookie-auth + no token lib + no SameSite)
pii_exposure unmasked PII at the output boundary res.json(rawEntity) with PII + a masking control defined but with zero live call sites (value-shape, not field-name)
graphql GraphQL surface introspection (AppSync introspectionConfig: DISABLED-aware) / playground / depth-limit + AppSync subscription-authz (cross-group BOLA)
integrations third-party + webhooks + outbound-action endpoints unsigned webhooks + email/SMS/push handlers with no auth or IP-only rate-limit + redundant secret-fetch
llm_security LLM / AI-agent surface (OWASP LLM Top 10) indirect prompt injection (untrusted RAG/tool content → prompt) · insecure output handling (model text → tool dispatch) · excessive agency · unbounded generation (no maxTokens/timeout) · guardrail fail-open
crypto_usage crypto-API correctness weak password hash (fast/unsalted SHA-256/MD5) · jwtVerify without an algorithms allowlist · predictable principal (id = hash of email) · non-constant-time secret compare
agent_config the repo's OWN agent/MCP wiring (OWASP Agentic Top 10) reads .claude/settings.json · .mcp.json · cursor/copilot rules as untrusted data (never executed): invisible/bidi Unicode rules-backdoor · fetch-and-execute hook (CVE-2025-59536) · blanket MCP auto-approve · *_BASE_URL override (key-exfil) · unpinned MCP server · committed secret in an MCP env/headers block (value-shape, ${VAR}-safe)
dependencies offline supply-chain hygiene (AI slopsquat class) malicious install/lifecycle script (fetch-and-exec postinstall) · lockfile drift (manifest dep absent from the lockfile) · unpinned + dependency-confusion names (advisory-only) · registry/typosquat resolution behind opt-in --network

Plus derived targeting — IDOR / SSRF / open-redirect / upload / write / auth-endpoint candidates — so probes get pointed at the exact endpoints, not fired blindly.

What you get (websec-out/)

Artifact What it is
AGENT-BRIEFING.md The product. Marching orders: detected surface, the access-control map, targeting, findings, the method, and the staged probe list.
FACTS.json The full structured recon.
findings.json Static scanner results, de-duplicated across tools and severity-ranked (with --scan).
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).
results.sarif SARIF 2.1.0 — always written. Drop it into GitHub Code Scanning (inline PR-diff annotations + the Security tab), GitLab, Azure DevOps, VS Code's SARIF viewer, DefectDojo.
findings.envelope.json A versioned, self-describing JSON envelope (schema_version) around the ledger — for non-GitHub CI / dashboards that shouldn't reverse-engineer the internal shape.
sbom.cdx.json A CycloneDX SBOM (with --sbom; --sbom spdx for SPDX) — the dependency inventory for SLSA / EO 14028 supply-chain gates, and the substrate a downstream scanner can rescan without re-walking the tree.
attack-surface.json The ranked per-endpoint planning table (routes × auth × sinks × risk + the reasons) — briefing §3a.
diff-scope.json With --diff: changed files + exact added/modified line ranges, so any reviewer can validate a finding sits on a changed line.
probes/ The probe scripts selected + staged for this app (BOLA, JWT, SSRF, mass-assignment…).

The flow

🔧 websec (deterministic)              🤖 your agent + 🧑 you
─────────────────────────────────      ─────────────────────────────────
1. recon → full attack surface     →   confirm the tenant boundary + auth model
2. run + de-dup static scanners    →   triage real-vs-noise
3. stage tailored probes           →   fill placeholders, run vs a TEST instance
4. emit AGENT-BRIEFING.md           →   propose fixes, re-run to confirm, report back

Static recon + briefing need only the code. Running the probes needs a live test instance + test credentials (the human supplies them) — the tool itself never touches a running app.

CI / enterprise integration

The recon is the same either way — these just make the output consumable by pipelines, dashboards, and non-Claude agents. All stdlib, no new dependency.

SARIF → GitHub Code Scanning. Every run writes results.sarif (SARIF 2.1.0). Upload it and each finding lands inline on the PR diff and in the Security tab, ranked by a security-severity band, with its CWE/ASVS/OWASP citation and remediation.

Gate the build. --fail-on {critical,high,medium,low} exits non-zero when a finding at or above that severity remains — a real CI gate, report-only by default.

Only fail on what the PR introduced. --baseline <prior findings-ledger.json> marks every finding new / unchanged / fixed (a stable per-finding fingerprint, surfaced as SARIF baselineState), and --fail-on then counts only the new ones — so a legacy backlog doesn't block every PR, but a newly introduced SSRF does.

Drop-in GitHub Action (action.yml):

# .github/workflows/security.yml
name: security
on: [pull_request]
permissions:
  contents: read
  security-events: write        # required to upload SARIF to Code Scanning
jobs:
  websec:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: raccioly/websec-validator@v0.10.0   # pin to a release tag
        with:
          path: .
          fail-on: high         # block the PR on a new HIGH+ (omit for report-only)
          # baseline: .websec/baseline-ledger.json   # optional: gate only on NEW findings

Local guardrail — websec hooks. Same baseline-diff, run from git instead of CI, so a new lead is caught before it ever reaches a PR:

websec hooks install              # advisory post-commit: prints "baseline: N new" after each commit (~1s, never blocks)
websec hooks install --pre-push   # blocking gate: fails `git push` on a NEW finding at/above $WEBSEC_HOOK_FAIL_ON (default high)
websec hooks status               # show what's installed
websec hooks uninstall

The hook appends to (and cleanly removes from) any existing hook, pins its interpreter so it works under pipx/uv isolation, and honors WEBSEC_SKIP_HOOK=1 for a one-off override. WEBSEC_HOOK_SCAN=1 runs the full static scanners in the hook (slower); the default is fast recon-only.

MCP server (any agent, not just Claude Code). websec mcp speaks the Model Context Protocol over stdio, exposing typed tools — websec_recon, websec_findings, websec_sarif, websec_briefing — so Cursor / Cline / Windsurf / Zed can call recon directly instead of shelling out and parsing stdout. Register it in your MCP client:

{ "mcpServers": { "websec": { "command": "websec", "args": ["mcp"] } } }

Or serve it over HTTP so a whole team points one URL at the recon tools (stdlib only — no extra dependency): websec mcp --http (binds 127.0.0.1:8733; GET /health for a load balancer, POST JSON-RPC for calls). It reads local paths and runs recon on them, so it binds to localhost by default — only expose it (--host 0.0.0.0) on a trusted network.

Blast-radius from a knowledge graph (opt-in, zero-dep). If your repo has a graphify graph at graphify-out/graph.json (or you pass --graph <file>), websec tags each finding with how much of the app transitively depends on the vulnerable code — so a SQLi in a leaf handler and the same SQLi in a helper imported by 40 modules stop looking equally urgent:

websec run . --scan     # auto-detects graphify-out/graph.json if present

Each mapped finding gains a graph block (blast_radius, a dependents sample, community) in findings-ledger.json, and the ledger a graph_enrichment summary. It reads the graph as plain JSON — it never imports tree-sitter, so websec stays stdlib-only, zero runtime deps — and a missing, malformed, or oversized graph is silently skipped, never failing the run.

Versioned contract. FACTS.json, findings-ledger.json, and findings.envelope.json all carry a schema_version; the JSON Schemas ship in the package (schemas/facts.schema.json, schemas/ledger.schema.json) so downstream tooling can validate against a stable shape.

Proof harness

websec proof clones a vuln-app corpus (VAmPI, NodeGoat, DVGA) and scores whether recon surfaces each app's documented attack surface — a deterministic, CI-trackable proxy (currently 10/10). The real kill-criterion (does the briefing lift an agent's bug-finding vs a generic prompt?) is the manual A/B in corpus/PROOF-PROTOCOL.md. Full methodology, calibrated precision numbers, and the competitor-comparison protocol: BENCHMARKS.md.

Calibrated confidence

websec calibrate runs the ledger against the labeled corpus, measures how often each (attack-class, confidence) bucket is a real documented vuln, and writes calibration.json (shipped + applied at runtime). Each finding then carries P(real) with a 95% Wilson confidence interval and the sample size n — so "MEDIUM" stops being a vibe and becomes "real ~57% of the time on the corpus (CI 43–70%, n=51)". A finding that matches no documented vuln counts as a false positive (the corpus is well-documented). Honest caveats: the corpus is deliberately vulnerable, so the rates skew optimistic for clean production code, and small samples mean wide intervals — the CI is the headline, not the point estimate, and both tighten as the corpus grows. With thin data a bucket falls back to the per-label aggregate, then to a clearly-flagged uncalibrated prior. No ML, no deps — binomial proportion + Wilson interval; the structure upgrades to isotonic regression if a large labeled set ever exists.

It self-improves. websec dynamic is an oracle: a write that executes unauthenticated is a confirmed real vuln, and a recon-flagged endpoint that turns out auth-enforced is a confirmed false positive. Every dynamic run folds those confirmed labels into a local overlay (~/.cache/websec-validator/, gitignored, never shipped) that's merged on top of the public table — so the numbers personalize to your apps the more you run it, with no extra step and nothing leaving your machine. To label by hand instead, feed a {attack_class, confidence, is_real} file to websec calibrate --ingest.

Dynamic phase (v2 — read-only so far)

When you have a running TEST instance, websec dynamic mints role tokens and runs the probes the static recon pointed at. v1 is read-only: authenticated cross-tenant BOLA on the group-scoped GET endpoints recon discovered.

cp dynamic-config.example.json dynamic-config.json    # TEST target + role creds (gitignored)
websec run ./my-app                                    # static recon → websec-out/FACTS.json
websec dynamic --config dynamic-config.json --facts websec-out/FACTS.json
# → "14/14 cross-tenant GET reads blocked — all isolated"   (or 🚨 LEAK with the exact endpoint)

Never point it at production. Write-verb BOLA, JWT/auth attacks, and a ZAP/Nuclei two-role diff are the next dynamic probes (explicitly gated — they mutate).

Validated on

A production Next.js app, a large Express/AWS monorepo, and the VAmPI / NodeGoat / DVGA vuln-app corpus — independently reproducing a hand-done pentest's findings (tenant boundary, SSRF, file upload, cross-tenant BOLA, role/authz gaps).

Tests

python3 -m unittest discover -s tests    # stdlib only, no Noir/network — 324 tests

Releasing (maintainer)

Published to PyPI via Trusted Publishing (OIDC — no API token in the repo). To cut a release:

# 1. bump the version in pyproject.toml (e.g. 0.2.1 → 0.2.2)
# 2. tag it and push — the tag must match pyproject's version (CI verifies):
git tag v0.2.2 && git push origin v0.2.2
# → publish.yml builds, INSTALLS + smoke-tests the wheel (version match,
#   calibration ships, a real `websec run`), then publishes. A bad build fails
#   CI instead of reaching PyPI — so you never have to yank after the fact.

One-time PyPI setup (before the first release): on pypi.org → Account → Publishing → Add a pending publisher with project websec-validator, owner raccioly, repo websec-validator, workflow publish.yml, environment pypi. The project is created on the first successful publish.

Two independent channels, two update mechanisms: the CLI ships to PyPI (semver releases, pip install --upgrade); the Claude Code plugin ships from git (tracks latest commit, refreshed via /plugin marketplace update).

Status / roadmap

Done: 20-extractor recon (incl. a WebExtension client-trust extractor — client-side entitlement gate / over-broad host permissions / world:"MAIN" / unvalidated external messages — a license/entitlement verification-trust pass — revocation-bypass + no per-license usage cap, provider- agnostic — Deno/Supabase-edge + Chrome-extension stack & route modeling, an authz-correctness data-flow extractor — unsigned-cookie / claim-keyed authz / transaction-local RLS — plus CORS-misconfig, SRI, host-header open-redirect and SSRF-redirect-hardening classes, schema/entity → mass-assignment targeting, the AWS-CDK / managed-AppSync / VTL boundary, upload-security + PII-output-boundary + redirect-SSRF

  • password-reuse classes, a man-in-the-browser / tamperable-display class, an LLM / AI-agent extractor (OWASP LLM Top 10 — prompt injection / insecure output / excessive agency / unbounded generation / guardrail fail-open), a crypto-usage extractor (weak password hash / jwtVerify-without- algorithms / predictable principal), docker-compose host-takeover + .gitleaksignore secret-suppression audits, and a reverse-proxy prefix-escape detector), cross-tool de-dup + bundled Semgrep rules, router-mount-auth modeling (cuts the dominant Express-monorepo missing-auth false positive), tailored probe staging, agent briefing, traceable findings ledger with calibrated confidence (CJE — Wilson CIs), proof harness, test suite (285), Docker bundle (all scanners + Noir, arch-aware), dynamic phase v1 (authenticated read-only cross-tenant BOLA — validated live, reproduced a hand-pentest's 14/14). Validated against the REF-PENTEST pen test + retest and re-validated on a large real-world LLM-agent monorepo (HIGH-finding noise 178 → 15, AI + crypto surfaces newly covered). Next: dynamic write-verb BOLA + JWT/auth probes + ZAP/Nuclei two-role diff (gated, they mutate), calibration on hand-labeled real repos (more representative base rate), ASVS index lookup, optional model-SDK adapters for no-agent fallback.

Using it as a Claude Code skill / plugin

This repo is a Claude Code plugin. Install it once —

/plugin marketplace add raccioly/websec-validator
/plugin install websec-validator@websec-plugins

— and the bundled security-pass skill (skills/security-pass/SKILL.md) lets you just ask, in plain English, for a security pass: it runs websec, reads the briefing, and works the findings with you. For other agents the universal interface is unchanged: run the CLI, read AGENT-BRIEFING.md.

Install gotchas (field-tested):

  • The install id is plugin@marketplacewebsec-validator@websec-plugins (the marketplace name from .claude-plugin/marketplace.json), not @websec-validator (the repo).
  • The plugin only delivers the instructions; the actual scanning is a separate Python CLI (websec). The skill's Step 0 installs it (pipx install websec-validator) if it's missing.
  • /plugin … only works in the terminal CLI. In the Claude app / Agent SDK (no /plugin), configure it in .claude/settings.json instead:
    {
      "extraKnownMarketplaces": {
        "websec-plugins": { "source": { "source": "github", "repo": "raccioly/websec-validator" } }
      },
      "enabledPlugins": { "websec-validator@websec-plugins": true }
    }
    
    This registers + enables the plugin but does not auto-fetch it — the first download still needs the CLI (/plugin install websec-validator@websec-plugins) once. (Project .claude/settings.json for a team; ~/.claude/settings.json for just you.)

Credits

Methodology + probe library are distilled from a real authenticated penetration-testing pass. This tool productizes that hand-written methodology into something an AI agent can run on any repo.

License

MIT © Ricardo Accioly

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

websec_validator-0.12.0.tar.gz (407.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

websec_validator-0.12.0-py3-none-any.whl (352.1 kB view details)

Uploaded Python 3

File details

Details for the file websec_validator-0.12.0.tar.gz.

File metadata

  • Download URL: websec_validator-0.12.0.tar.gz
  • Upload date:
  • Size: 407.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for websec_validator-0.12.0.tar.gz
Algorithm Hash digest
SHA256 7a4fd462427b7a95a75e7071bb40c23bf990711629ad78014aa9c29593c324e3
MD5 c831f98d0d80fbdd84dbb2e8220c87fe
BLAKE2b-256 cf8f72be5df457f3d427b857576dfdb80a5d99c6df1a8ed4a28a97ceb349174b

See more details on using hashes here.

Provenance

The following attestation bundles were made for websec_validator-0.12.0.tar.gz:

Publisher: publish.yml on raccioly/websec-validator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file websec_validator-0.12.0-py3-none-any.whl.

File metadata

File hashes

Hashes for websec_validator-0.12.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91757329a551f37404d7d89a81beee7aa5478d7d1ea476b15871cb1a3b46657e
MD5 418e35eaae852f405b234cf7e3e48bd6
BLAKE2b-256 0b439d2b4628253f5f7ae13aeffa7c51db3c5706f3a3422d86206ce67cd7984c

See more details on using hashes here.

Provenance

The following attestation bundles were made for websec_validator-0.12.0-py3-none-any.whl:

Publisher: publish.yml on raccioly/websec-validator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.15.3

2 files

0.15.2

2 files

0.15.1

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

This release

0.12.0 This release

2 files

0.11.0

2 files

0.10.0

2 files

0.9.1

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.3

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.0

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.9

2 files

0.2.8

2 files

0.2.7

2 files

0.2.6

2 files

0.2.5

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page