Skip to content

Repository files navigation

Centinela — Authorization-Gated Web Security Scanning Agent

An agent for authorized, non-destructive web security testing. It acts like a junior pentester: it orchestrates standard tools (Nuclei, testssl.sh, OWASP ZAP), interprets their output — optionally with Claude — and produces an actionable report in JSON, Markdown and PDF.

Python FastAPI LangGraph Claude PostgreSQL SQLAlchemy Next.js Docker

Non-negotiable principle: no network request leaves the system without first passing the scope gate. Authorization is earned, not declared — a real domain is only scannable once its ownership has been proven and a signed, in-force consent exists.

🇪🇸 A detailed Spanish version of this document is kept in the git history and available on request.


Why this project is interesting

Most scanners will happily fire at any target you point them at. Centinela is built around the opposite premise: the hard part of ethical security testing is authorization, not scanning. The whole system is designed so that it is structurally impossible to touch a real domain you have not proven you own and consented to test.

Key features

  • Hard scope gate. The graph always starts at validate_scope. Without valid authorization it halts before executing a single network node.
  • HttpGuard. A single HTTP client that every outbound request must go through; it enforces the scope allowlist on each call and logs every contact. Anything off-allowlist → OutOfScopeError.
  • Domain-ownership verification (ACME-style). DNS TXT record or an HTTP file under /.well-known/, the latter fetched through the guard via a single-use challenge grant.
  • Tamper-evident consent. A SHA-256 hash over a canonical Rules-of-Engagement document; if the document is altered after consent, the gate invalidates it.
  • Non-destructive scanning. Nuclei with -exclude-tags dos,intrusive,fuzz, rate limiting, timeouts and no out-of-band callbacks by default.
  • Pluggable analysis. With an API key, Claude consolidates CVSS, OWASP Top 10 and remediation. Without a key, a deterministic raw mode maps only what the scanners actually return and marks everything else as "not available" — it never invents findings.
  • Reporting in JSON, Markdown and PDF (cover with authorization reference, severity summary, detail and numbering).
  • Append-only audit trail of every decision, tool and host contacted.

Architecture

The core is a LangGraph state graph:

flowchart LR
    START --> validate_scope
    validate_scope -->|unauthorized| END
    validate_scope -->|ok| recon
    recon --> passive_checks
    passive_checks -->|active_scan| active_scan
    passive_checks -->|intrusive_eligible| dast_scan
    passive_checks -->|passive| analyze
    active_scan -->|intrusive_eligible| dast_scan
    active_scan -->|not intrusive| analyze
    dast_scan --> analyze
    analyze --> report --> END
Loading
Node Responsibility
validate_scope Hard gate. Local targets via a self-authorized path; real domains require in-force verification + valid consent + time window.
recon Passive fingerprinting (server, technology, headers) via HttpGuard.
passive_checks Security headers (HSTS, CSP, X-Frame-Options…) and TLS via testssl.sh.
active_scan Nuclei (only if the scope authorizes it).
dast_scan OWASP ZAP in a confined Docker container (dedicated network, memory cap, scope context). Only if intrusive_eligible.
analyze LlmAnalyzer (Claude) or RawAnalyzer (no AI). Consolidates findings from all three layers and deduplicates by (CWE, URL).
report Assembles and persists the report; declares methodology, consent level and limitations.

Tech stack

Layer Technology
Backend Python 3.12, FastAPI, LangGraph, Anthropic SDK, httpx, dnspython
Analysis Claude (Anthropic) · deterministic raw mode (no AI)
Tooling Nuclei, testssl.sh, OWASP ZAP (official Docker image)
PDF report Jinja2 + WeasyPrint
Persistence PostgreSQL + SQLAlchemy (in-memory by default in dev)
Frontend Next.js 15, TypeScript, Tailwind CSS
Dev infra docker-compose (backend + postgres + Juice Shop)

Project structure

backend/app/
├── main.py            # FastAPI app
├── config.py          # settings (pydantic-settings)
├── graph/             # LangGraph state, orchestrator and nodes (scope, recon, passive, scan, dast, analyze, report)
├── models/            # scope, finding, report, verification, consent, authorization, audit, run
├── repositories/      # data-access layer (append-only audit)
├── tools/             # http_guard, nuclei, testssl, zap, verification, consent, analyzers, pdf, llm
└── templates/         # HTML template for the PDF report
backend/scopes/        # example scopes (local Juice Shop)
backend/scripts/run_scan.py   # scan CLI
frontend/              # Next.js 15 (guided flow + report)
docker-compose.yml     # backend + postgres + juice-shop

Getting started

Requirements: Python 3.12+, Node.js 18+ (frontend), Docker (for Juice Shop, Nuclei, testssl.sh, ZAP and PDF generation). Optional: CENTINELA_ANTHROPIC_API_KEY for Claude analysis — without it, raw mode is used automatically.

# With docker-compose
docker compose up -d juice-shop postgres
docker compose run --rm backend python scripts/run_scan.py --scope scopes/juice-shop-docker.yaml

# Backend locally
cd backend
pip install -e ".[dev]"
python scripts/run_scan.py --scope scopes/juice-shop.yaml --no-ai   # passive + Nuclei, no AI
uvicorn app.main:app --port 8000                                     # API

# Frontend
cd frontend && npm install && npm run dev   # http://localhost:3000 (API on :8000)

Key environment variables: CENTINELA_ANTHROPIC_API_KEY (Claude key; raw mode without it), CENTINELA_NUCLEI_TEMPLATES_DIR, and the CENTINELA_ZAP_* knobs (MEMORY, ATTACK_STRENGTH, SPIDER_TIMEOUT_MIN, ASCAN_TIMEOUT_MIN) that bound the DAST stage. Secrets are read from the environment only — never committed.

Real-domain flow (UI): register domain → choose method → publish the challenge → verify → capture authorization + consent → scope becomes authorized → launch scan → download PDF. The UI only guides; the source of truth is the backend gate.


API

Method Path Description
POST /scans Launch a background scan
GET /scans/{id} Status (running/done/error) and report
GET /scans List scans
GET /scopes Available scopes (local + real domains)
POST /targets Register a domain and return the challenge
POST /targets/{id}/verify Verify ownership (DNS/HTTP)
POST /authorizations Create the ROE document for a verified domain
POST /authorizations/{id}/consent Record consent (hash)
GET /scans/{id}/report.pdf Download the PDF report

Tests

cd backend
pytest -q       # 65 tests (+1 skip: WeasyPrint smoke on environments without native libs)
ruff check .

Tests cover the scope gate, the HttpGuard, verification (mocked DNS/HTTP), consent and its hash check (including tampering), raw mode, the API, and an end-to-end passive run against a simulated Juice Shop (Claude mocked, no API calls).


Honest results (first full run)

The first end-to-end intrusive-level run against OWASP Juice Shop (raw mode, on an 8 GB dev VPS) produced 467 deduplicated findings across the passive, Nuclei and ZAP layers — including an exposed Prometheus /metrics endpoint, a public Swagger spec, CORS misconfiguration (136 resources) and missing CSP (112 URLs).

Notably, SQLi/XSS did not appear — and the report says so and explains why: under the RAM budget the DAST stage ran ZAP with HtmlUnit and only 2 ascan threads, which cannot drive Juice Shop's Angular SPA the way headless Chrome would. Surfacing those known injections requires ≥ 5 GB free for ZAP + Chrome. Documenting this limitation transparently — rather than hiding it — is a deliberate design goal.


What I learned building this

  • Modeling authorization as a state machine: ownership verification, tamper-evident consent, and time windows enforced before any network activity — security by construction, not by convention.
  • Orchestrating a multi-stage pipeline as a LangGraph state graph with conditional routing.
  • Wrapping untrusted, resource-hungry tooling (OWASP ZAP) in a confined Docker sandbox with memory caps, timeouts and a dedicated network.
  • Designing an AI-optional system: Claude enriches analysis when available, but a deterministic fallback keeps the product honest and functional without it.
  • Writing a report layer that declares its own limitations — a habit that matters as much in security as it does in engineering.

Legal notice

Centinela is designed for authorized security testing. Use it only against systems you own or have explicit, in-force permission to test. Misuse is the sole responsibility of the operator.

About

Authorization-gated AI agent for non-destructive, authorized web security scanning (Nuclei, testssl, OWASP ZAP) with JSON/MD/PDF reports.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages