Truffle's Base Rates Make the Threshold Decision Arithmetic
GitHub shipped the operating discipline for LLM classifiers with no numbers attached, and a separate re-verification of four years of leaked credentials supplies the loss function it was missing.
Price the miss before tuning the threshold
Start with the survival curve. Truffle Security re-verified 10,616 AWS keys leaked publicly between August 2022 and August 2026. 88% still authenticate. Of the roughly 9,342 live keys, 768, about 8.2%, carry full administrative control, and the median compromised key had gone roughly five years without rotation. Conditional on a leak, the hazard of a credential expiring on its own is effectively zero. The curve is flat, which leaves time-to-detection as the only controllable variable.
Put those quantities into an objective and the F1-maximising threshold stops being defensible for a secret detector. F1 prices a false positive and a false negative identically. Here the false negative is a live credential with probability 0.88, roughly one in twelve of them admin. The false positive costs analyst-minutes. The defensible operating point is maximum recall at a fixed triage budget, the budget set by what a false alarm actually costs in human time. Re-deriving that threshold for a detector already in production is about two days of work, and among the highest-leverage two days available.
What GitHub supplied, and what it left out
GitHub's four principles are unglamorous, which is the recommendation: decide the product question before picking the metric; run evaluation as a continuously executed integration test rather than a pre-launch gate; keep offline conditions close to production; use targeted error analysis on repeat failure patterns instead of watching an aggregate score. The thing this doesn't tell you is which practice moved anything: no precision or recall deltas, no baseline, no ablation. Credible playbook from a team running a real high-stakes surface. Not evidence.
| Practice | Common team default | What changes in your harness |
|---|---|---|
| Metric follows the product question | F1 or accuracy chosen by habit | Explicit cost matrix; threshold optimised for expected loss |
| Eval as a CI test | Notebook run before a model swap | Suite fires on every prompt, model and retriever change; merge blocks on regression |
| Offline conditions match production | Curated golden set, clean inputs | Replayed traffic, real truncation, real latency budget, real index |
| Targeted error analysis | Aggregate score on a dashboard | Named failure slices, each with its own pass threshold |
The estimator defect both reports share
Techpresso's model-scanner comparison hits the same problem from another angle. One tool returned verdicts on all 135 test files; another judged roughly half, and accuracy was reported only over the rows where a verdict was emitted. That is a selection-bias confound: abstentions get laundered out of the headline number, so the score describes a self-selected easy subset. Coverage-adjusted is the right figure, accuracy multiplied by verdict rate, abstentions counted as misses. Most LLM-as-judge harnesses share the defect, wherever an "unclear" verdict is dropped before the rate is computed. Two independent reports, one estimator bug.
An accuracy figure computed only over the rows a detector agreed to answer describes the easy inputs, not production data.
The other half of this is in-house keys
Those base rates describe internal stacks too. Static credentials sit in notebook environments, dbt profiles, Airflow and Dagster connections, MLflow, and feature-store service accounts, and nothing in a data platform forces rotation. Short-lived workload identity is the structural fix; budget alerts and a cost-anomaly monitor cover the window before it lands. One honest limit on transferability: Truffle's population is publicly exposed keys, so 0.88 is conditional on public exposure and should not be applied unchanged to an internal key that never left the network boundary.
What to do
Re-derive the decision threshold for your highest-stakes classifier this week using an explicit cost matrix seeded with P(live | leaked) = 0.88 and the 8.2% admin tail, and report expected loss per candidate threshold instead of F1.
Move the eval suite into CI by end of sprint with per-slice pass thresholds that fail the merge on regression, and change the scoring code to count abstentions and 'unclear' judge rows as misses.
Audit the oldest static access key in the data stack — notebooks, dbt profiles, Airflow and Dagster connections, MLflow, feature-store service accounts — and replace it with short-lived workload identity within two weeks.