<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>AI Security | Marcin Wylot, PhD</title>
    <link>https://mwylot.net/tags/ai-security/</link>
      <atom:link href="https://mwylot.net/tags/ai-security/index.xml" rel="self" type="application/rss+xml" />
    <description>AI Security</description>
    <generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Mon, 10 Aug 2026 00:00:00 +0000</lastBuildDate>
    <image>
      <url>https://mwylot.net/media/logo_hu_e1fbd36c83cd87fc.png</url>
      <title>AI Security</title>
      <link>https://mwylot.net/tags/ai-security/</link>
    </image>
    
    <item>
      <title>How Do You Trust a Number an LLM Extracted?</title>
      <link>https://mwylot.net/post/trust-a-number-llm-extracted/</link>
      <pubDate>Mon, 10 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://mwylot.net/post/trust-a-number-llm-extracted/</guid>
      <description>&lt;p&gt;A pipeline returns a perfectly-shaped JSON object. Every field present, every type valid, Pydantic green across the board, not a parse error in sight — and the number inside it is wrong. Not malformed. Wrong. The model misread a figure, or invented one, or pinned it to the wrong entity, and nothing in the system can tell, because nothing in the system was ever built to ask. The output was indistinguishable from a correct output, so it shipped as one. Somewhere downstream, a person made a decision on it. If you have shipped an extraction pipeline, this one probably looks familiar — it is the most common way a demo that dazzled becomes a number nobody can defend.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Schema-valid&amp;rdquo; and &amp;ldquo;true&amp;rdquo; are different words, and most AI pipelines only check the first one. That is the whole problem, compressed. The honest worry underneath &amp;ldquo;we can&amp;rsquo;t trust the numbers it extracts enough to act on them&amp;rdquo; is one the model itself can never answer — because you would be asking the thing that might be wrong to certify that it isn&amp;rsquo;t, and no amount of prompting fixes that. Trust isn&amp;rsquo;t a property you prompt into a model; it&amp;rsquo;s a property you engineer downstream of it, in layers your own code owns and the model never gets to author. The model proposes. Something you build disposes. What follows are four of those layers, climbing from cheap to authoritative — each drawn from a system running in production, and each one you can adopt on its own, in the order your case actually needs.&lt;/p&gt;
&lt;h2 id=&#34;a-well-shaped-number-can-still-be-wrong&#34;&gt;A well-shaped number can still be wrong&lt;/h2&gt;
&lt;p&gt;Validating the shape of structured output is table stakes now, and it&amp;rsquo;s worth having. Pydantic tool-calling gives you real guarantees: the fields you asked for exist, the types match, and the whole free-text-parsing class of bug — the regex that breaks the first time the model adds a stray comma — is simply gone. Keep doing it. Every layer below assumes it.&lt;/p&gt;
&lt;p&gt;But shape validation does exactly nothing about a hallucinated &lt;em&gt;value&lt;/em&gt;. A number that is confidently, precisely wrong is perfectly well-shaped; it passes every schema check you have, because schema checks were never asking whether the value is true — only whether it&amp;rsquo;s the right &lt;em&gt;type&lt;/em&gt; of thing to be wrong about. The gap between &amp;ldquo;well-shaped&amp;rdquo; and &amp;ldquo;actually correct&amp;rdquo; is where a confidently-wrong number lives, and it&amp;rsquo;s the gap most pipelines never close. Closing it is four escalating moves, cheap to expensive. You rarely need all four at once — the useful thing is knowing which one your case calls for, so here they are in order.&lt;/p&gt;
&lt;h2 id=&#34;stop-letting-one-pass-grade-its-own-work&#34;&gt;Stop letting one pass grade its own work&lt;/h2&gt;
&lt;p&gt;The cheapest move is to stop letting a single extraction pass certify its own output. A model asked to both &lt;em&gt;find&lt;/em&gt; a candidate fact and &lt;em&gt;judge&lt;/em&gt; whether it&amp;rsquo;s true will happily rate its own inventions highly — those are two jobs, and they want two stages.&lt;/p&gt;
&lt;p&gt;
 does exactly that: it turns live broadcast transcripts into broadcast-quality game narratives across nine categories, and every extracted element is handed to an independent judge stage whose only task is suspicion. The independence that matters here is structural — not a second vendor&amp;rsquo;s model sitting in judgment, just a separate call with a separate prompt whose entire job is to look for reasons to reject, so nothing grades its own homework inside a single pass. The judge checks five things: every fact must trace to a specific source quote; every player name must match the official roster; every number must match confirmed final statistics; nothing may be added that the commentator never said — no invented timing, outcome, or location; and any category-specific requirement must hold. An element that fails any check is dropped, with its reason logged, before it reaches the final set.&lt;/p&gt;
&lt;p&gt;That logging is worth more than it looks. It&amp;rsquo;s the difference between a filter and an audit trail — between &amp;ldquo;the system removed some things&amp;rdquo; and &amp;ldquo;here is exactly what it removed, and why.&amp;rdquo; The drop rate isn&amp;rsquo;t a number worth quoting, because it depends on the game — a noisy overtime broadcast sheds more than a clean blowout — and that dependence is the point: it&amp;rsquo;s a number you get to watch, not one you have to take on faith. If you build only one of these four layers, build this one.&lt;/p&gt;
&lt;h2 id=&#34;when-something-else-can-confirm-the-number-let-it&#34;&gt;When something else can confirm the number, let it&lt;/h2&gt;
&lt;p&gt;The second move applies whenever a value can be independently confirmed. When it can, the model is not your system of record, and it doesn&amp;rsquo;t get a vote.&lt;/p&gt;
&lt;p&gt;Broadcast speech-to-text fails in a specific, dangerous way: it mishears numbers. It captures a &amp;ldquo;45&amp;rdquo; as &amp;ldquo;42,&amp;rdquo; or grabs a partial mid-game count and presents it as a final total. Left alone, a model faithfully extracts the wrong number — and it&amp;rsquo;s &lt;em&gt;well-shaped&lt;/em&gt; wrong, a plausible integer in a field that expects one. The fix isn&amp;rsquo;t a cleverer prompt; it&amp;rsquo;s a policy that authoritative stats win, enforced at two points in the same pipeline. At extraction time, the prompt carries an explicit priority rule: confirmed final statistics are authoritative and always override whatever the transcript seems to say. And the same judge from the first move enforces it on the way out — its number-check rejects any figure that doesn&amp;rsquo;t match the confirmed stats, so a misheard &amp;ldquo;42&amp;rdquo; that should be &amp;ldquo;45&amp;rdquo; gets dropped rather than shipped.&lt;/p&gt;
&lt;p&gt;The move worth carrying into your own architecture is simple to state: walk your extracted values and ask which of them have an authoritative source somewhere in &lt;em&gt;your&lt;/em&gt; systems — a ledger, a database of record, a canonical feed. For every value that does, the model is a proposer, never the final authority. Whatever you can confirm, you confirm, and the confirmed source wins. The model only gets to be the system of record for the things nothing else can check.&lt;/p&gt;
&lt;h2 id=&#34;check-what-a-value-is-before-it-becomes-durable-state&#34;&gt;Check what a value &lt;em&gt;is&lt;/em&gt; before it becomes durable state&lt;/h2&gt;
&lt;p&gt;The third move is the easiest to defer and the most expensive to skip, and it&amp;rsquo;s the one that turns a correctness problem into a security one. Validate what the values &lt;em&gt;are&lt;/em&gt; — not just their type — and do it hardest right before anything becomes durable state, because durable state is what gets read back and acted on later.&lt;/p&gt;
&lt;p&gt;Two systems, one principle. In an 
, the model reads each user&amp;rsquo;s entire conversation history and distills durable, evolving insights that are &lt;em&gt;persisted and fed back into future prompts&lt;/em&gt;. That feedback loop is exactly where an injected instruction would do lasting damage: the conversation text is fully user-controlled, so in a naive design a prompt injection buried in a message could write fabricated vocabulary into someone&amp;rsquo;s permanent profile and steer every future run. It can&amp;rsquo;t here, because the model&amp;rsquo;s output is allow-listed before it&amp;rsquo;s ever stored — every principle and behavioral dimension it emits is checked against a controlled catalog and dropped if it isn&amp;rsquo;t a member, and the user&amp;rsquo;s text is escaped so a message can&amp;rsquo;t close the transcript wrapper and pose as instructions. The contents are vetted, not just the shape — secure by design, not a filter bolted on after an incident.&lt;/p&gt;
&lt;p&gt;For a fintech, the same instinct guards a database. An 
, and the generated SQL is parsed — every table reference extracted and rejected if it isn&amp;rsquo;t on an approved allow-list, every write, DDL, and privilege command scanned for and refused. But the application-level check is deliberately not the last line. Underneath it sits a read-only database role with no BYPASSRLS privilege, so PostgreSQL Row-Level Security is enforced in the kernel: even under total application compromise, the model physically cannot read another tenant&amp;rsquo;s rows. Five independent layers, the innermost one holding when every outer one has failed — no cross-tenant incidents to date, and the part worth trusting isn&amp;rsquo;t the clean log, it&amp;rsquo;s the reason for it: a guarantee that lives in the database kernel rather than in hope. Shape validation was never a security boundary. Contents allow-listing, backed by a control the model can&amp;rsquo;t reach past, is.&lt;/p&gt;
&lt;h2 id=&#34;let-your-code-own-provenance-and-your-people-own-the-last-call&#34;&gt;Let your code own provenance, and your people own the last call&lt;/h2&gt;
&lt;p&gt;The fourth move is a refusal: never let the model author facts about itself. Where did this extract come from — which conversation, which user, which moment in time? The model must not be the one to answer, because a misattributed fact is a wrong fact in correct clothes. In that 
, conversation identity and timestamps are stamped by code, never taken from model output. The model proposes &lt;em&gt;content&lt;/em&gt;; the system owns provenance, so an extract can never be quietly reassigned to the wrong person.&lt;/p&gt;
&lt;p&gt;The other end of provenance is a person. A 
 — high-stakes documents where a wrong figure outlives any demo. Every finding is forced through a Pydantic contract with required fields — source, location, time_period, category, detail — and a finding missing any of them isn&amp;rsquo;t a finding. Then it passes five explicit human-in-the-loop checkpoints — category assignment, post-parse review, pre-generation context confirmation, post-generation edit, and iterative regeneration — before a report ships, and every generated section carries provenance back to a source file and page number so a reviewer can check a claim against the document in seconds instead of hunting for it. Those checkpoints aren&amp;rsquo;t an admission that the AI is weak. They&amp;rsquo;re a decision about where a human&amp;rsquo;s judgment is worth more than throughput — which, for a report an engineer signs their name under, is exactly where you want the friction.&lt;/p&gt;
&lt;h2 id=&#34;the-economics-checking-is-cheap-next-to-being-confidently-wrong&#34;&gt;The economics: checking is cheap next to being confidently wrong&lt;/h2&gt;
&lt;p&gt;The reflex objection is that all this sounds expensive — a judge call on every element, a parse on every query, five checkpoints in the loop. It&amp;rsquo;s worth being concrete, because most of the cost is smaller than the reflex assumes. Allow-listing a value against a controlled vocabulary is an in-memory set lookup. Parsing a query for forbidden commands and unapproved tables is nearly free. Row-Level Security is enforced by the database you&amp;rsquo;re already paying to run. The judge call is the one real added cost — a second LLM pass per element — and it&amp;rsquo;s small next to what it stands in front of: a decision that would otherwise ride on an unchecked number.&lt;/p&gt;
&lt;p&gt;The human layer is the honest exception, and it&amp;rsquo;s worth naming rather than glossing. Five checkpoints on a civil-engineering report aren&amp;rsquo;t cheap. They&amp;rsquo;re a deliberate spend, placed exactly where an engineer is about to sign their name — and every automated layer around them exists so that expensive human attention lands only where it truly has to.&lt;/p&gt;
&lt;p&gt;It helps to name the real cost function. A confidently wrong number that ships is expensive precisely because its cost lands later, downstream, after someone has acted on it. That reframes failing closed from timidity into arithmetic. In the map-reduce pipeline, when a batch&amp;rsquo;s error rate crosses a threshold, the synthesis stage simply doesn&amp;rsquo;t run — the system produces nothing that cycle and reprocesses next time, rather than synthesizing an insight from partial coverage and calling it complete. Nothing is a cheap, honest output; a plausible answer built on a corrupted input is the costly one. Staying correct under failure — returning nothing rather than something plausible and unfounded — is how the whole approach stays cost-bounded at scale: the automated checks are small and constant, the human check is deliberately placed, and the disaster they foreclose is the one with no ceiling.&lt;/p&gt;
&lt;h2 id=&#34;one-idea-underneath-all-four-the-model-proposes-your-code-disposes&#34;&gt;One idea underneath all four: the model proposes, your code disposes&lt;/h2&gt;
&lt;p&gt;Step back and the four moves are one idea. Verify with an independent judge. Treat a confirmed source as the authority. Allow-list the contents before they persist. Let code own provenance, and people own the last call. In every case the model proposes and something you built — a separate judge, a code-owned gate, a human reviewer — disposes; the model never gets the final say over its own output.&lt;/p&gt;
&lt;p&gt;And wherever that disposing layer is pure code, it has a property the model never will: you can test it. In the map-reduce pipeline, the deterministic core — the partitioning, the window and epoch merging, the tally accumulation, the generation-hash preimage — is unit-tested with no model in the loop at all. The part of the system responsible for trust is exactly the part you can prove correct on a laptop, deterministically, before it ever meets a token.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the reframe worth keeping. &amp;ldquo;Can we trust the number this model extracted&amp;rdquo; isn&amp;rsquo;t really a question about the model, and it isn&amp;rsquo;t one you settle by buying a better one. It&amp;rsquo;s a question about the architecture around the model — whether someone wrapped it in layers that verify, reconcile, constrain, and attribute, or whether it hands its output straight to a decision and hopes. The first is a production system.&lt;/p&gt;
&lt;h2 id=&#34;a-test-you-can-run-before-you-act-on-any-extracted-number&#34;&gt;A test you can run before you act on any extracted number&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s an honest test you can apply to your own pipeline today. If it extracts numbers that feed a decision, a report, a customer-facing surface, or any state you write down and read back later — and the only thing between the model and that consequence is a schema check — then you&amp;rsquo;ve validated that the output is well-shaped and validated nothing about whether it&amp;rsquo;s true. That&amp;rsquo;s the gap worth closing before more depends on it, and most of it you can close with the first three moves above — none of which needs a better model.&lt;/p&gt;
&lt;p&gt;This is one failure mode among several that quietly sink AI projects. It sits underneath the bigger question of why a demo that dazzles on ten documents still 
 — the cost, coverage, and correctness-under-retry failures that share this one&amp;rsquo;s DNA. And if the security end is what keeps you up — the injection buried in user text, the output that poisons state you&amp;rsquo;ll re-read tomorrow — that&amp;rsquo;s the deeper audit, where prompt injection turns out to be a persistence problem more than a prompt one. If that gap is open in a pipeline you own, it&amp;rsquo;s a better conversation to have before the number ships than after.&lt;/p&gt;</description>
    </item>
    
  </channel>
</rss>
