Git: https://github.com/motornomad/postura.git
There's a SQL injection in your codebase. Bandit flagged it as B608, severity MEDIUM. Semgrep agrees. It goes in the backlog. It sits there.
Here's the problem: that SQL injection might be the most critical vulnerability in your entire application — or completely irrelevant. Your scanner literally cannot tell you which.
A SQL injection behind session auth, JWT validation, and IP allowlisting, reading from a logging database with no personal data? That's LOW. The same SQL injection behind a public unauthenticated endpoint, querying a users table with emails and password hashes? That's CRITICAL.
Static tools score both the same. POSTURA is an attempt to fix this.
Vulnerabilities Are Graphs, Not Lists
Every security tool I've used produces a flat list sorted by severity. But real-world exploitability is topological:
- Can an attacker reach it? Is there a call-graph path from a public endpoint to the vulnerable function?
- What does it touch? Does it read from a data store containing PII?
- What chains with it? Does a missing input validation on one endpoint compose with a SQL injection downstream to create an attack path that neither finding represents alone?
These are graph questions. You can't answer them with a list. So I built a graph.
What POSTURA Is
POSTURA is a self-hosted service (4 Docker containers — Neo4j, Redis, FastAPI, Celery worker) that hooks into your GitHub workflow and maintains a persistent threat graph of your codebase.
The flow: GitHub webhook fires on push/PR → changed files parsed via Tree-sitter → static tools (Semgrep, Bandit) run as inputs → Neo4j graph updated incrementally → LLM agent reasons about risk using the graph → PR comment posted with contextual severity and fix suggestions.

The key data structure is the threat graph — 7 node types and 10 edge types:
Nodes: Service, Endpoint, Function, DataStore, Dependency, Finding, TrustZone
Edges: CALLS, HANDLED_BY, READS_FROM, WRITES_TO, USES, AFFECTS, IN_ZONE, TRUSTS, BELONGS_TO, and the critical one — CHAINS_TO.

That last edge is where compositional risk lives.
The CHAINS_TO Edge
Static tools find individual findings. POSTURA discovers chains — sequences of findings that compose into a larger risk no individual scanner can express.
A concrete example from the test suite. A Flask app with three findings:
- Finding A:
GET /admin/usershas no@login_required(CWE-306) - Finding B:
get_user_by_name()uses an f-string SQL query (CWE-89) - Finding C: The
userstable contains emails and password hashes
Bandit finds B. It doesn't find A (missing auth is the absence of a pattern). It has no concept of C (data classification isn't static analysis).
POSTURA builds the graph and discovers a path: public unauth endpoint → handler → SQL injection → PII datastore. It creates :CHAINS_TO edges. Contextual severity: CRITICAL — even though B alone was MEDIUM.

The chain is invisible to any tool that looks at files in isolation.
Incremental Updates
POSTURA doesn't re-scan your entire codebase on every commit. The webhook layer computes the minimal change scope — which files changed, their transitive dependents — and only those get re-processed.
The graph engine snapshots the affected subgraph, marks it stale, re-ingests changed files, rebuilds, and computes a diff. The agent sees what changed, not the full graph.

This is what makes POSTURA usable as a CI check rather than a batch job.
The Reasoning Agent
The reasoning layer is a LangGraph ReAct agent with 6 tools. It receives the graph diff and autonomously decides what to investigate:
- graph_query — read-only Cypher against Neo4j
- trace_dataflow — follow call-graph paths from source to sink
- find_chains — traverse
:CHAINS_TOedges - assess_exploitability — retrieve trust zone, reachability, PII exposure
- knowledge_retrieve — hybrid search over 900+ CWE entries, CVE data, OWASP Top 10
- generate_remediation — code fix with diff
The agent isn't following a script. Different diffs trigger different tool sequences. The output is a structured PRSecurityReview — risk level, chains, contextual severity, remediations — posted as a GitHub PR comment.
Contextual Severity
The same finding gets different scores depending on where it sits in the graph.

The posture score aggregates all findings into a single 0–100 number using severity-weighted exponential decay. Designed to not collapse at the extremes.
Evaluation
Tested on three codebases. No aspirational numbers.
Self-analysis (POSTURA on its own source): 228 functions, 1311 call edges, 17 endpoints. 11 findings (subprocess usage, CORS — expected). 0 chains. Score: 100. Parse: 0.26s.
Deliberately vulnerable app (dvpwa): SQLi flagged CRITICAL. Weak crypto detected. Score: 15/100. Limitation surfaced: aiohttp endpoint extraction not supported (Flask/FastAPI only), so 0 chains despite individual findings.
Clean codebase (Python requests): 634 functions, 2348 call edges. 0 false chains. Score: 59/100 (Bandit noise — assert_used in test files).

Note: The fixture comparison is on a purpose-built test app. Real-world recall will differ as the test corpus expands.
Testing surfaced 6 bugs in POSTURA itself — a Cypher syntax error blocking chain discovery, a PATH issue causing SAST tools to silently return 0 findings, a posture formula that collapsed to 0 beyond ~20 findings. All fixed before release.
What I'd Build Next
Taint analysis. Currently POSTURA uses call-graph reachability. True taint tracking — does user input from this parameter flow through this variable into this SQL string — would improve chain precision significantly.
Multi-language support. Tree-sitter is language-agnostic; the bottleneck is per-framework endpoint extractors (Django, Express, Spring, Go).
Interactive graph explorer. Neo4j Browser works but isn't built for security teams. A purpose-built UI with chain highlighting and NL query would make POSTURA accessible beyond developers.
Try It
~12,000 lines of Python. 30 source files.
git clone https://github.com/motornomad/postura.git
cd postura
docker compose up -d neo4j redis
pip install -e ".[dev]"
uvicorn postura.api.app:app --reloadPoint it at your codebase. See what chains it finds.
If you're working on security tooling, graph-based analysis, or LLM agents for code — connect on LinkedIn.