Probanza — Validation Evidence

A static auditor for AI systems. What it detects, how well, on what — and what it deliberately does not claim.

Generated from labeled data · drift fails the build
3823
Hand-labeled findings
20
Real products, pinned
20
Rules measured
5
Doctrines refused

In plain terms

what it finds, and why it matters

An AI system fails in ways ordinary code review does not catch. Probanza reads a codebase and finds those places before they ship.

It is a static auditor: it finds the exposure, it does not stop an attack. Every count below is what it found in real, shipping open-source products — hand-labeled, one finding at a time.

It can be steered by the content it reads

28found

If your assistant summarizes a web page, a support ticket, or an uploaded document, whoever wrote that text can hide instructions inside it. Probanza finds the places where outside text reaches the model's own instructions with nothing separating the two — the boundary that stops a retrieved document from talking to your agent as if it were you.

Found across 6 real products. Measured by PROMPT_INJECTION_UNTRUSTED_BLENDING · PROMPT_INJECTION_SECRECY_POLICY · PROMPT_INJECTION_CROSSFILE_BLENDING — precision 1.000.

Its answers are trusted without being checked

1254found

A model's output flows onward into a database write, a workflow, another API call — with nothing verifying it is even the right shape. Probanza finds those unguarded hand-offs: the model calls and prompts whose result no schema, validator, or parser ever inspects.

Found across 11 real products. Measured by AI_MODEL_CALL_WITHOUT_VALIDATOR · PROMPT_MISSING_STRUCTURED_OUTPUT · AI_PROMPT_WITHOUT_VALIDATOR — precision 0.991.

Business decisions are hiding inside prompts

6found

Pricing thresholds, eligibility rules and scoring formulas written into a prompt are decided by a language model — differently each time, with no audit trail, and invisible to the tests that cover your code. Probanza finds the rules that drifted out of your codebase and into English.

Found across 4 real products. Measured by PROMPT_CONTAINS_DETERMINISTIC_FORMULA — precision 1.000.

Ordinary exposure, ranked by what it actually costs you

58found

Hardcoded credentials, unsafe evaluation, shell execution — the familiar risks, with the familiar problem: a scanner that shouts about every one of them trains your team to ignore all of them. Probanza detects each, then classifies what kind it is, so a key your vendor publishes on purpose never arrives wearing the same red as a leaked one.

Found across 3 real products. Measured by HARDCODED_CREDENTIALS · UNSAFE_EVAL_USAGE · SHELL_EXECUTION — precision 1.000. Of the 17 credentials it found across these products, 0 are security incidents.

Reading the numbers

The tables below use the vocabulary of detection measurement. It decodes to four ideas:

True positive
The tool flagged something, and it was real.
False positive
The tool flagged something that was not a problem. This is the number that decides whether your team trusts the tool or learns to ignore it.
False negative
A real problem the tool missed.
Precision
Of everything it flagged, how much was real.
Recall
Of everything real, how much it caught. Both run 0 to 1; higher is better.

A tool can look perfect by flagging almost nothing — high precision, terrible recall — or by flagging everything, which is perfect recall and unusable noise. Both numbers are published here for every rule, which is the only way either one means anything.

Measured detection quality

precision · recall · labeled n

Every number is computed from hand-labeled CSVs at build time. None is transcribed by a person, and the build fails if this page drifts from the labels. That is not fastidiousness: prose did drift here once, and it hid a real false positive.

RuleTPFPFN Labeled setPrecisionRecall nRecord
ACCESS_CONTROL_UNSCOPED_OBJECT_LOOKUP 1900 1.0001.00019 2026-07-13l · bundle
AI_AGENT_SHELL_WITHOUT_APPROVAL 151 0.1671.0000.5007 2026-07-14r · bundle
AI_MODEL_CALL_WITHOUT_VALIDATOR 120094 0.9930.9971213 2026-07-14u
AI_PROMPT_WITHOUT_VALIDATOR 2220 0.9170.9171.00024 2026-07-13f · bundle
HARDCODED_CREDENTIALS 17530 0.2431.0001.00070 2026-07-12c
PROMPT_CONTAINS_DETERMINISTIC_FORMULA 600 1.0001.0006 2026-07-14u
PROMPT_INJECTION_CROSSFILE_BLENDING 2401 1.0000.96025 2026-07-13n · bundle
PROMPT_INJECTION_SECRECY_POLICY 300 1.0001.0003 2026-07-12j · bundle
PROMPT_INJECTION_UNTRUSTED_BLENDING 100 1.0001.0001 2026-07-12j · bundle
PROMPT_MISSING_STRUCTURED_OUTPUT 3220 0.9411.00034 2026-07-14u
SHELL_EXECUTION 100 1.0001.0001 2026-07-12c
SHELL_EXECUTION 3000 1.0001.00030 2026-07-14r · bundle
TEST_GAP_DETECTED 5100 1.0001.00051 2026-07-13f · bundle
TEST_GAP_DETECTED 7500 1.0001.00075 2026-07-14q · bundle
TEST_GAP_DETECTED 14450 0.9661.0001.000149 2026-07-14v · bundle
TEST_GAP_DETECTED 80060 0.9930.9991.000806 2026-07-14y · bundle
TEST_GAP_DETECTED 158260 0.8591.0001.000184 2026-07-14ab · bundle
TEST_GAP_DETECTED 799490 0.9421.0001.000848 2026-07-14ah · bundle
TEST_GAP_DETECTED 229270 0.8950.9471.000256 2026-07-16b · bundle
UNSAFE_EVAL_USAGE 11100 0.5241.0001.00021 2026-07-12c

true positive false positive false negative bar shows the shape of the labeled set

The arrows are the point

Two rules carry an arrow. They are rules whose false positives we found in our own tool and fixed. The left number is what the rule actually scored when we labeled it; the right is what it scores after the fix.

We publish both, because a product that only ever shows you its final number is asking you to trust that it looked.

  • AI_AGENT_SHELL_WITHOUT_APPROVAL5 false positives found and eliminated at 2026-07-19b: precision 0.167 → 1.000 over 1 surviving true positives.
  • AI_PROMPT_WITHOUT_VALIDATOR2 false positives found and eliminated at 2026-07-17a: precision 0.917 → 0.917 over 22 surviving true positives.
  • HARDCODED_CREDENTIALS53 false positives found and eliminated at 2026-07-12c: precision 0.243 → 1.000 over 17 surviving true positives.
  • TEST_GAP_DETECTED5 false positives found and eliminated at 2026-07-14x: precision 0.966 → 1.000 over 144 surviving true positives.
  • TEST_GAP_DETECTED6 false positives found and eliminated at 2026-07-14aa: precision 0.993 → 0.999 over 800 surviving true positives.
  • TEST_GAP_DETECTED26 false positives found and eliminated at 2026-07-14ae: precision 0.859 → 1.000 over 158 surviving true positives.
  • TEST_GAP_DETECTED49 false positives found and eliminated at 2026-07-16a: precision 0.942 → 1.000 over 799 surviving true positives.
  • TEST_GAP_DETECTED27 false positives found and eliminated at 2026-07-17a: precision 0.895 → 0.947 over 229 surviving true positives.
  • UNSAFE_EVAL_USAGE10 false positives found and eliminated at 2026-07-12c: precision 0.524 → 1.000 over 11 surviving true positives.

Did it generalize?

the overfitting test

A rule can score beautifully on the code its author was staring at while writing it. The only honest test is code the rule has never seen — so five products (browser-use, private-gpt, open-webui, LibreChat, Flowise) were acquired after the same-file rules were built, and those rules met them cold.

RuleDesigned onBenchmark TPAcquired TPAcquired FPWhat the acquired slice tests
PROMPT_INJECTION_SECRECY_POLICY the 15 benchmark products 3 0 0 specificity — silence, verified correct
PROMPT_INJECTION_UNTRUSTED_BLENDING the 15 benchmark products 1 0 0 specificity — silence, verified correct
PROMPT_INJECTION_CROSSFILE_BLENDING all 20 products 16 8 0 nothing — in-sample by construction

What this proves. Run cold against 5 unfamiliar products and 5,592 files, the same-file rules raised 5 false positives — and a manual source-to-prompt census confirmed the silence was correct: those products defend their prompts properly, delimiting untrusted text and carrying it in its own message role. The rules did not overfit to noise. Pointed at unfamiliar, well-built code, they had the good sense to say nothing.

What this does not prove — and we will not pretend otherwise. They also found nothing new there. Zero true positives in the acquired slice makes this a test of specificity, not of discovery. It shows the rules do not invent findings in code they have never read; it does not show they would find real ones.

The cross-file rule does find real things across unfamiliar products — 8 of its 24 true positives live outside the benchmark, at 1.000 precision, so it is plainly not a two-product special case. But it was designed with all twenty products already in view. Its numbers are in-sample by construction, and it has not yet had a true out-of-sample test. Claiming one would be the cheapest lie in this document.

The experiment that would settle it

Acquire products the cross-file rule has never seen, run it cold, and label every fire by hand — the same way everything else here was measured. Until that runs, the honest answer to "does it generalize?" is: the specificity half is proven; the discovery half is not.

Across every rule, the acquired slice contributes 45 true positives and 5 false positives.

Detection is not risk

2026-07-12t

The credential rule detects credentials. Whether a credential is an incident is a second question — and answering only the first is how security tools earn their reputation for noise.

A PostHog project key is a hardcoded credential. The vendor also publishes it on purpose, to be embedded in client code. Calling that CRITICAL is not a detection error; it is a risk-classification error. So the rule keeps detecting every credential, and a taxonomy decides what kind it is.

Critical Private credential — secret material, immediately exploitable 0
High Production credential — bound to a live system 0
Medium Environment-dependent — risk depends on what fills it 0
Low Development credential — dev containers, CI fixtures, vendor defaults 5
Info Public by design — the vendor publishes it on purpose 12

Across the corpus's 17 detected credentials

0 are security incidents. The rest are true findings that need no action — and they are still reported, with their class, because deleting them would destroy traceability.

What they turned out to be:

  • PostHog Project API Key × 11
  • Default Development Credential × 3
  • Development Credential × 2
  • Mixpanel Project Token × 1

Two properties keep the taxonomy honest. A recognized secret format beats context: an AWS key inside a CI workflow is still a leaked AWS key, and a dev-looking location never rescues real secret material. And ignorance never downgrades: a credential the taxonomy does not recognize stays CRITICAL as unclassified, which makes that count a quality metric rather than a hiding place.

What that costs a team

A security tool's real price is not its licence. It is the hours an engineer spends triaging what it says — and a tool that shouts about everything teaches its users to hear nothing.

122
Alerts it used to raise
on this corpus
63
False alarms removed
by measurement
59
Still detected today
nothing was lost
42
Actually require action
after classification

Two arcs did this without weakening detection by a single finding: one removed the rules' false positives, the other stopped calling public-by-design credentials incidents. A team triaging the security rules across these twenty products would have opened 122 alerts. Today they open 4266% fewer, and every true positive is still found.

What this number is not

It is measured against this tool's own earlier behaviour — not against another vendor's scanner. We have never run one, so we do not claim a ratio against one. And the number belongs to this corpus; what generalizes is the mechanism, not the percentage.

The negative record

measured, then refused

This is the section most detection products cannot write, because writing it requires having measured something and then thrown it away. Each of these was a plausible improvement that a reasonable engineer would have shipped on intuition.

2026-07-12i · bundle

Weak-delimiter refinement — treat quote-delimited untrusted text as undefended

Measured at ~0.06 precision on the corpus and rejected. It would have turned a clean rule into a noise generator.

2026-07-12o · bundle

Promotion of the cross-file rule on a claimed precision of 1.000

The promotion review re-adjudicated every fire against the labeled baseline and found an unlabeled false positive (a RAG-indexing chunk). Real precision was 0.955, not 1.000. The promotion was STOPPED, the false positive was rooted out, and only then did the rule ship.

2026-07-12q · bundle

Dataset dict-field provenance — close the last false negative by trusting a ["context"] subscript

Rejected: it resurrected the false positive 12o had just closed, by laundering a weak signal through the gate that trusts genuine ingestion. The false negative stays registered instead.

2026-07-12t

Exonerating PostHog keys — silence the analytics-key credential findings

Rejected as the wrong axis. The DETECTION was right; the RISK CLASSIFICATION was wrong, and an exception list neither scales (Segment, Sentry, Amplitude, LogRocket, Bugsnag are the same shape) nor keeps traceability. Built a classification LAYER instead: the rule still detects every credential, and a taxonomy decides what kind it is. Detection moved by zero findings.

2026-07-12r · bundle

Sanitizer recognition — treat an escape/strip step before a prompt as exculpatory

Censused across all 20 products and closed as UNMOTIVATED. Not one sanitizer in the corpus defends prompt content; they defend filesystems, databases, encodings, secrets and XSS. Measured, a file-scoped exoneration is a no-op and a repo-scoped one destroys every true positive.

What it finds in real products

exposure surfaces

The cross-file injection rule's true positives are real sites in shipping open-source products: external retrieved content — scraped pages, fetched documents, RAG results — reaching a prompt instruction with no instruction/data boundary.

These are architectural observations, not vulnerability reports. For a research agent, blending scraped text into a prompt is the product; the finding names the boundary that is missing and the exposure that follows from its absence. We make no exploitability claim, ship no proof-of-concept, and have not filed these as security issues.

They are here because they reproduce: pin the commit, run the auditor, get the same findings.

The access-control rule is a different matter, and we treat it as one. A missing authorization check on an object-id handler is a vulnerability class (IDOR), not an architectural style — so the number on the table above is reported as a measurement of the rule, over a corpus adjudicated for precision, and nothing here names a file, a line, or a route in any product. We have not assessed exploitability against any deployment and have not notified maintainers, so this page does not identify the handlers. What the rule can see is the code-shaped half only: it reads the absence of a check in a repository, never a live deployment — the other half of the breach that motivated the rule (a real product's admin account with the password 123456) is invisible to any static scan, and the page says so plainly under What it cannot see.

What it cannot see

a miss, in full

Every detection vendor publishes what it catches. Here is one it does not — the real code, the real reason, and the real attempt to fix it.

A boundary a tool states is worth more than a capability it claims, because only one of the two can be checked.

MetaGPT — metagpt/ext/aflow/benchmark/hotpotqa.py:53 registered 2026-07-12q · bundle
paragraphs = [item[1] for item in problem["context"] if isinstance(item[1], list)]
context_str = "\n".join(" ".join(paragraph) for paragraph in paragraphs)
inputs = f"Context: {context_str}\n\nQuestion: {input_text}\n\nAnswer:"
What it is
Retrieved Wikipedia paragraphs — content the system did not write — are pasted straight into a prompt instruction with no boundary between them and the question. It is the same shape as the 24 sites the rule does catch.
Why Probanza misses it
The rule fires on external content only when it can PROVE the content is external: a real fetch, a scrape, a retriever call, a document field, or a prompt that declares its own source. Here the text arrives from a dataset dictionary — `problem["context"]` — and the file makes no external call at all. The provenance gate, which is the thing holding this rule's precision at 1.000, has nothing to grab.
Why it is still open
We built the fix and measured it. Treating a `["context"]` subscript as ingestion closed this miss — and RESURRECTED the false positive that the previous arc had just paid to close, because it laundered a weak signal through the gate that trusts genuine ingestion. So the fix was rejected and the miss stays registered. It is not a limitation we suffer; it is a trade we chose, with the numbers in front of us: one known miss beats one unknown false alarm.

That is the shape of every boundary here. Not a gap we have not gotten to — a trade made with the numbers in front of us: one known miss beats one unknown false alarm, because the miss is registered and the false alarm is what teaches your team to stop reading the output.

Declared boundaries

what a null result does not mean

Where the auditor cannot see, a finding's absence means nothing. That list is maintained as a first-class artifact, not an afterthought:

  • Languages — TypeScript/JavaScript and Python only. Go, Java, Ruby and Rust are unsupported.
  • Notebooks (.ipynb) are scanned but carry no labeled rows. Unmeasured.
  • Static analysis only — no code is executed. Dynamic imports and framework-specific runtime wiring can escape the uses graph. Every audit report carries this in its uncertainty block.
  • Registered false negatives are listed per rule, each with the arc that measured it.

The corpus

20 products · pinned commits

Not synthetic fixtures — shipping code with real users, chosen because it contains the surfaces the rules target: model calls, prompt templates, retrieval pipelines, agent tool-use.

Benchmark slice (15) — the labeled ledger's lineage: aider, files-to-prompt, gpt-researcher, instructor, langfuse, mem0, MetaGPT, openai-node, openai-python, openai-structured, prompt-cleaner-mcp, prompt-file-examples, Prompt-Injection-Playground, screenshot-to-code, vercel-ai.

Acquired slice (5)CommitTracked files
browser-use68afe46456476
Flowisebb773ffa712444
LibreChatcf9a426d293588
open-webuiecd48e2f714960
private-gptbcadb418f3885

Two tiers of evidence

what is downloadable, and what is on request

Not every label behind this page is downloadable, and we would rather state the split than let you discover it.

The public tier — the commodity detections: credentials, unsafe evaluation, shell execution, and the model-call validator family — ships its full ground truth in this repository: 1345 labeled findings, every CSV beside the dated record that produced it, recomputable by verify.py.

The gated tier — the differentiated detectors: the injection family, access control, the test-gap and agent-shell rules — publishes its numbers, its boundaries and its registered misses on this page, and ships its ground truth — 2478 labeled findings — as an evaluator bundle, under NDA, on request. The bundle carries the labels, the full dated records, and its own copy of the verify script that recomputes every gated number the same way.

The reason is stated plainly rather than buried: the labeled corpus is the asset a copy of this product would need most. Handing the commodity part to everyone costs nothing; handing the differentiated part to anonymous readers is a disclosure decision, and we made it the way this project makes every decision — recorded, with the trade named (decision 004). An evaluator with a name gets everything.

Evaluating Probanza?

Request the evaluator bundle — say who you are and what you are evaluating.

Request access →

Check it yourself

do not trust this table

A measurement document that asks to be believed has failed. So here is everything needed to disbelieve this one.

The public tier's labeled data is in this repository. Every finding behind the public rows above — 1345 of them, each marked true positive, false positive or false negative by hand — is committed as CSV beside the dated record that produced it.

Recompute the numbers without taking our word for anything. A dependency-free script reads those CSVs and prints their table rows from scratch:

git clone https://github.com/reoxb/probanza-evidence
cd probanza-evidence
python3 verify.py          # recomputes the public tier's precision and recall from the labels

If its output disagrees with what you read above, the page is wrong and you should say so.

The gated tier is checkable the same way — by named evaluators. The bundle carries the remaining 2478 labeled findings and its own verify script. A number you cannot recompute from this repository is marked bundle in the table, never smuggled past you.

Every public-tier claim links to the dated record that grounds it; gated rows name their arc. The corpus is pinned to exact commits, so the code we audited is the code you would fetch.

The engine is the product. Its homework is licensed reading.

Probanza's detection engine is commercial and its source is closed, so no, you cannot run it from this repository. That is the trade — stated in one sentence, rather than buried in a footnote.

What we will not do is ask you to take the rest on faith. The commodity labels, the public records, the pinned corpus, the doctrines we measured and threw away, and the failures we cannot see are all here — and everything gated is one named request away, checkable by the same script.

A vendor confident in its product can afford to show its homework — and to say which pages of it are licensed reading. That is the argument of this page, and it is the only one we intend to make.

Want to see what it finds in your codebase?

Concierge audits are open — bring a repository. You get the findings and the evidence behind each one — including the ones we get wrong.

Get your repo audited →