Attack Surface Posture Agent — agentic security graph for your codebase
Project description
POSTURA
Agentic Attack Surface Posture Intelligence
POSTURA is an agentic security system that maintains a persistent Neo4j threat graph of your codebase, updating incrementally on every commit. Unlike static analysis tools that examine code in isolation, POSTURA reasons about compositional risk — vulnerability chains that emerge from how endpoints, functions, data stores, and dependencies interact at runtime.
Why POSTURA
Static tools (Bandit, Semgrep) find individual vulnerabilities but miss the context:
| What static tools miss | How POSTURA closes the gap |
|---|---|
| Missing auth (CWE-306) — no static test for "should have auth" | Auth graph edge absence + public endpoint flag |
| Contextual severity — code severity ≠ runtime severity | Graph paths: exposure × datastore sensitivity |
| Supply-chain CVE reachability — CVE known, call-path unknown | USES edges: Function→Dependency + endpoint reachability |
| SQLi + PII chains — B608 finds SQLi, but not the PII path | CHAINS_TO: Finding→DataStore(PII=true) |
| Dependency blast radius — not modeled anywhere | Graph traversal: Dependency→Function→Endpoint |
On the included vulnerable Flask fixture, Bandit detects 4/6 findings (67%), all at incorrect severities, and 0/3 vulnerability chains. POSTURA targets 6/6 findings and 3/3 chains.
Architecture
GitHub webhook
│
▼
WebhookReceiver (FastAPI)
│ push / PR event
▼
ScopeAnalyzer ──► git diff → changed files
│
▼
AST Parser (tree-sitter)
├─ ASTNode (functions, classes)
├─ CallEdge (function→function)
├─ DataAccessEvent (SQL/ORM reads)
└─ ImportedPackages (top-level)
│
▼
GraphBuilder / GraphUpdater (Neo4j)
Nodes: Finding · Endpoint · Function · DataStore · Dependency · TrustZone
Edges: HANDLED_BY · CALLS · AFFECTS · CHAINS_TO · USES · READS_FROM · IN_ZONE
│
▼
Reasoning Layer (LangGraph ReAct agent + 6 tools)
├─ graph_query — read-only Cypher
├─ knowledge_retrieve — BM25 + ChromaDB hybrid search (CWE/CVE/OWASP)
├─ trace_dataflow — endpoint → function → datastore paths
├─ find_chains — vulnerability chain discovery
├─ assess_exploitability — context-aware severity scoring
└─ generate_remediation — LLM-powered fix suggestions
│
▼
Delivery Layer
├─ GitHub PR comment (POST /repos/.../issues/.../comments)
├─ GitHub commit status (POST /repos/.../statuses/...)
└─ Posture history (PostureSnapshot nodes in Neo4j)
│
▼
REST API (FastAPI)
/api/v1/posture /api/v1/findings /api/v1/chains
/api/v1/endpoints /api/v1/dashboard /api/v1/history
/api/v1/trend /api/v1/query /api/v1/knowledge/*
Quickstart
Prerequisites
- Docker (for Neo4j and Redis)
- Python 3.11+
1. Start infrastructure
docker run -d --name neo4j \
-e NEO4J_AUTH=neo4j/postura_dev \
-p 7474:7474 -p 7687:7687 \
neo4j:5
docker run -d --name redis -p 6379:6379 redis:7-alpine
2. Install POSTURA
python3.11 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]" --no-cache-dir
3. Configure
cp .env.example .env
# Edit .env:
# POSTURA_NEO4J_URI=bolt://localhost:7687
# POSTURA_NEO4J_USER=neo4j
# POSTURA_NEO4J_PASSWORD=postura_dev
# POSTURA_ANTHROPIC_API_KEY=sk-ant-...
# POSTURA_GITHUB_TOKEN=ghp_... (for PR comments)
4. Bootstrap knowledge base
# OWASP Top 10 (offline, always works)
curl -X POST "http://localhost:8000/api/v1/knowledge/reload?sources=owasp"
# CWE + CVE (requires network — downloads MITRE XML and NVD API)
curl -X POST "http://localhost:8000/api/v1/knowledge/reload?sources=cwe,cve"
5. Ingest a repository
# Start the API server
uvicorn postura.api.app:app --reload
# Start Celery worker (optional — for async analysis)
celery -A postura.tasks.celery_app worker --loglevel=info
# Point your GitHub repo's webhook at:
# http://<your-server>/webhook/github
# Content-Type: application/json
# Secret: $POSTURA_WEBHOOK_SECRET
REST API
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/posture |
Current posture score + finding counts |
| GET | /api/v1/findings |
All findings (filter by status, severity) |
| GET | /api/v1/findings/{uid} |
Finding detail with graph context |
| GET | /api/v1/findings/{uid}/chains |
Chains involving a finding |
| POST | /api/v1/findings/{uid}/remediate |
LLM remediation suggestion |
| GET | /api/v1/endpoints |
All endpoints with trust zone / auth |
| GET | /api/v1/chains |
All vulnerability chains |
| GET | /api/v1/dashboard |
Top risks, trend, chain count |
| GET | /api/v1/history |
Time-series posture history |
| GET | /api/v1/trend |
Posture trend over N snapshots |
| POST | /api/v1/query |
Natural language graph query |
| GET | /api/v1/diff/{sha} |
Findings introduced/resolved by commit |
| GET | /api/v1/dependencies/{name}/blast-radius |
Dependency blast radius |
| POST | /api/v1/knowledge/reload |
Reload CWE/CVE/OWASP knowledge |
| GET | /api/v1/knowledge/status |
Knowledge collection sizes |
| GET | /api/v1/knowledge/search |
Hybrid search across knowledge base |
Natural language query
curl -X POST http://localhost:8000/api/v1/query \
-H "Content-Type: application/json" \
-d '{"question": "Which public endpoints have no authentication?"}'
{
"question": "Which public endpoints have no authentication?",
"answer": "There are 2 public unauthenticated endpoints: GET /admin/users and GET /fetch. The /admin/users endpoint exposes PII from the users table without any authentication check (CWE-306).",
"cypher": "MATCH (e:Endpoint {is_public: true, auth_required: false}) RETURN e.path, e.method",
"result_count": 2,
"raw_results": [...]
}
Evaluation
# Static baseline only (no Neo4j required)
PYTHONPATH=. python evaluation/baseline_static.py
# Full comparison report (requires ingested graph)
PYTHONPATH=. python evaluation/report.py
# Save Markdown report
PYTHONPATH=. python evaluation/report.py --output evaluation/REPORT.md
Benchmark results on vulnerable Flask fixture (tests/fixtures/vulnerable_flask_app):
| Metric | Bandit | POSTURA |
|---|---|---|
| Findings detected (6 total) | 4 (67%) | 6 (100%) |
| Severity correct | ❌ 3/4 underrated | ✅ Contextual |
| Chains detected (3 total) | 0 (0%) | 3 (100%) |
| Missing auth (F2/CWE-306) | ❌ | ✅ |
| Supply-chain reachability | ❌ | ✅ |
Development
# Run all tests (skips e2e that need Docker)
PYTHONPATH=. .venv/bin/pytest tests/ -v
# Run evaluation tests only
PYTHONPATH=. .venv/bin/pytest tests/test_evaluation/ -v
# Type check
.venv/bin/mypy src/postura --ignore-missing-imports
# Lint
.venv/bin/ruff check src/
Phase Completion
| Phase | Description | Status |
|---|---|---|
| P1 | Foundation (models, ingest, AST parser) | ✅ |
| P2 | Threat Graph (Neo4j builder, chain discovery, severity scoring) | ✅ |
| P3 | Incremental Updates (webhook, git diff, updater) | ✅ |
| P4 | Reasoning Layer (LangGraph agent, knowledge base, 6 tools) | ✅ |
| P5 | Delivery & Reporting (GitHub integration, dashboard, NL query) | ✅ |
| P5.5 | Evaluation Framework (ground truth, baseline, report generator) | ✅ |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file postura-0.1.1.tar.gz.
File metadata
- Download URL: postura-0.1.1.tar.gz
- Upload date:
- Size: 82.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84c5dc1722846a897b75a238ff093757c6b437f2e95750ff6bc2f2a0f651fcfe
|
|
| MD5 |
ab870247a2b11d4459f467e03a56553a
|
|
| BLAKE2b-256 |
b4a296b1cc89d0faf41987ab61e1cae115d30987e31fe609bc9d315f57486aa8
|
File details
Details for the file postura-0.1.1-py3-none-any.whl.
File metadata
- Download URL: postura-0.1.1-py3-none-any.whl
- Upload date:
- Size: 95.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fd799df577de1f1d05ece77cfd0cb3a6a6616ab199fd374ae7ec30b7eb5783f
|
|
| MD5 |
5ec923aa2c14b4267a0ca40d53f67ef4
|
|
| BLAKE2b-256 |
0abf1181f3d01612388c783b54992ef6762e7c626f8d3e36cd2648dfe3d73f7f
|