How Questor-AI Works
VulnDNA – Questor-AI’s Flagship Tool
Contact usOn this page
VulnDNA Technical Description
Questor-AI’s flagship tool is VulnDNA, which uses a neuro-symbolic architecture to utilize AI capability within a deterministic security framework.
The term neuro-symbolic refers to the union of these two approaches. The symbolic side is the deterministic analysis: it finds the issues, records the facts behind each one, and has the final say. The neural side is the LLM: it reasons over those facts, and every claim it makes is checked against them before it can change a result. How this plays out across every stage of the security remediation cycle – find, prove, fix, verify, automate – is detailed below. If you have just 5 minutes, however, check out our video overview.
Find
VulnDNA starts by analyzing source code to build a complete graph of the application. From the parsed source it builds taint flows, control-flow graphs, SSA, a cross-file call graph, and function summaries. The analysis is interprocedural, field-sensitive, and flow-sensitive.
VulnDNA traces untrusted input from sources to dangerous sinks. Sources include HTTP parameters, bodies, and headers; WebSocket, GraphQL, and gRPC messages; files and archives; CLI arguments and environment variables; and data read back from storage. It follows those values through function calls, object fields, and collections, and reports flows that reach a sink unsanitized.
VulnDNA identifies several issue types at this stage: SQL and NoSQL injection, command injection, XSS, path traversal, XXE, insecure deserialization, code and expression injection, open redirect, LDAP, XPath, and XSLT injection, log injection, SSRF, SSTI, JNDI injection, prototype pollution, header manipulation, arbitrary file write, CSRF, CORS misconfiguration, race conditions, and auth bypass.
VulnDNA also surfaces security bugs that have no dangerous sink at all. Broken object-level authorization (IDOR), #1 on the OWASP API Security Top 10, is the clearest case. For these, VulnDNA reasons about authorization structure instead of data paths. It identifies the input that names a resource, the data access that uses it, and whether an ownership or authorization check gates that access. The check accounts for route guards, middleware guards, and ORM ownership relationships.
VulnDNA also determines if known CVEs apply to your third-party dependencies. Instead of flagging every known CVE in your dependency tree, it checks whether the vulnerable code is reachable from your application, and what an upgrade would affect.
Prove
VulnDNA gives every security issue candidate an explicit verdict, and every verdict shows its reasoning. An issue is either deemed real, as a finding with a proof trace, or refuted, with the specific reason it is safe. Nothing is silently dropped.
The verdict pipeline applies several checks, each recorded with a rationale:
- Provenance.
- Not every input is attacker-controlled. Network input is weighted differently from configuration values, CLI flags, or framework metadata, so a finding reflects real exploitability rather than any movement of data.
- Exposure.
- The engine checks whether the vulnerable code is reachable from an entry point, such as an HTTP route.
- Defenses on the actual path.
- Sanitizers, validators, parameterized queries, and output encoders are recognized, but only count if they lie on the control-flow path to the sink. A sanitizer somewhere else in the file does not refute the flow.
- Certainty.
- Each finding carries a certainty label and a confidence score, along with what the tool may have missed.
Findings are grouped as one issue per root cause, so a single flaw reached by several paths does not become several tickets.
Once a finding is identified, an LLM is used to validate it. Each request is scoped to one isolated finding. It is never a whole repository or a whole file. The model’s conclusions are checked against the engine’s graph facts. The model can’t change a result unless its reasoning holds up against those facts. This keeps confabulation out of the results. It also re-checks borderline refutations, so a defense that only looks effective can be challenged.
What a finding looks like
A SQL injection in WebGoat, detected with no build:
[CRITICAL] SQL Injection (CWE-89) in SqlInjectionChallenge.java
source line 46 @RequestParam -> provenance: Network (attacker-controlled), confidence 1.0
flow parameter `username` carries the tainted value into the query string
sink line 57 statement.executeQuery(checkUserQuery) -> SqlExecution
proof certainty: PROVEN finding confidence: 0.96
- User input detected (HttpInput source: @RequestParam)
- SqlExecution sink detected (executeQuery)
- Direct source-to-sink flow in the same function
- CFG-verified path: control-flow graph confirmed the path is reachableAn authorization finding in crAPI. There is no tainted flow here. The bug is the structure itself:
[CRITICAL] IDOR / broken object-level authorization in VehicleController.java:124
endpoint GET /vehicle/{carId}/location (getLocationBOLA) -> exposure: direct entry point
resource id carId, taken from the URL path
data access line 124: looks up the record identified by carId
gap no ownership check between the request identifier and the lookup
type Horizontal IDOR (one user can read another user's object)A finding also includes an attack scenario, an example payload, and the input flow step by step from route to sink.
Refutations name the defense that makes the code safe, such as Parameterized, Sanitized, Validated, FrameworkProtected, OrmProtected, or SafePattern, each with a short explanation.
Fix
An LLM is given a fix generation request that includes VulnDNA’s proof for the finding, which includes the findings context, the vulnerability class, the sink’s semantics, and the correct fix structure for that class in that language. The LLM’s result is then checked against the same proof.
For the SQL injection example, the target is a parameterized query:
// before (proven vulnerable)
statement.executeQuery("SELECT ... WHERE userid = '" + username + "'");
// after (grounded, guided fix)
PreparedStatement ps = conn.prepareStatement("SELECT ... WHERE userid = ?");
ps.setString(1, username);Each fix includes an explanation of why it was applied, where it is applied, and the files it touches.
Prompts are small and targeted, built from the proven finding rather than from whole files. That keeps token usage low enough to run on every pull request. VulnDNA fix generation is model-agnostic. You can use frontier models, Azure OpenAI, Amazon Bedrock, or open-weight models you host yourself, which puts cost, data residency, and privacy under your control.
By default, a fix is a proposal. You see the exploit scenario and the proposed patch in the portal or as a PR, and you decide whether to commit it.
Verify
After a fix is applied, VulnDNA re-scans the code with the same deterministic engine. If the proven flow no longer exists, the result is Fix Verified, along with the number of findings the change eliminated. The report also records the evidence strength behind the original finding and the pipeline status.
A fix that only moves the taint somewhere else, or doesn’t actually cut the flow, fails verification. This re-scan is the reliability gate that makes it reasonable to let fixes flow through CI.
Automate
You decide how much VulnDNA does on its own. Automation is set per finding category and verdict through a post-scan action:
| Post-scan action | What happens |
|---|---|
| None | Findings are reported. Nothing else happens. This is the default. |
| Generate fix | A verified fix is produced for your review. |
| Submit issue | A ticket is opened, one per root cause, with full context. |
| Issue + PR | A ticket is opened and a PR with the verified fix is raised against it. |
| Commit | A verified fix is committed and pushed directly. |
Fix generation for IDOR findings is coming; until then they can be reported or ticketed.
Most teams start by reviewing everything: the exploit scenario, the proposed fix, and the verification result. As a team gets comfortable with a class, it can move that class to Issue + PR, so fixes arrive as ordinary pull requests waiting for a merge.
Over time, what remains for human review typically narrows to the hard cases. These include findings the engine itself flags as too complex to fix alone.
