I don't automate the decision, I automate the fact that it expires
A CVE lands on a component you run. The faulty dependency was fixed upstream weeks ago, but the image publisher never rebuilt. You can't patch, you can't ship, and you can't forget. What happens next doesn't delegate to a model, and it's the best explanation I know of what AI cannot do.
A CVE lands on a component you run. The faulty dependency was fixed upstream weeks ago, but the image publisher never rebuilt. There is nothing to update. Your scanner, on the other hand, sees a published fix and refuses to let the deployment through.
You can't patch. You can't ship. And you can't forget.
That's where the real work starts, and that's exactly where people suggest putting an agent.
Detection is the easy problem
I get asked more and more often to build an assistant that reads advisories and answers whether we're affected. It demos beautifully. It also targets a seat that's already taken, by something free and reproducible.
A scanner runs over the images changed in a deployment, cross-checks what it finds against public databases, and returns a list. It understands nothing, and that's precisely what makes it useful: it compares. The same image twice gives the same list twice.
The agent guesses, for lack of a source of truth about what actually runs on your side. You can object that you just have to hand it the inventory, and that's where it gets interesting: it will have it, and it will compare version numbers in natural language. Deciding whether a version falls inside an announced range isn't a matter of understanding, it's an ordered comparison. A model gets it right often. Often is not an operational criterion.
Exploitability isn't in the advisory
Here's the real work. Once the list exists, you decide, line by line, whether the flaw is reachable on your side. And that information appears nowhere in the security advisory.
Three decisions I made and wrote down, as they stand.
An HTTP library targeted by a request smuggling attack. Except that in this deployment it is an outbound client stack. There is no inbound server behind a shared proxy, so the boundary the attack exploits doesn't exist.
A vulnerable PostgreSQL driver, bundled in a service whose query log is configured as CSV. No PostgreSQL target, no database credentials, no network path to a database for that service. The faulty code is present and never called.
An OpenSSL flaw in the handling of very large X.509 certificates on 32-bit builds. The image is pinned to linux/amd64. The vulnerable path sits outside the runtime target.
None of those three sentences exists in an advisory. They live in my configuration, and in what I know about how the service can be reached. A model that has neither cannot produce that reasoning. And even handed everything, it would still miss the essential part: when I write "path not used" in a versioned file, I'm signing it. It signs nothing.
What I automate isn't the decision
Accepting a risk is a judgment call, and it stays mine. What I automate is the fact that this judgment cannot be forgotten.
The mechanism is one format constraint. Every waiver carries an expiry date, and a check validates those dates before the scanner even starts. An expired waiver doesn't downgrade a severity, it breaks the delivery pipeline.
# Every entry carries an expiry and a reason. A validation script runs
# BEFORE the scan: a date in the past fails the pipeline, even though
# the CVE is listed right here.
CVE-2026-XXXXX # expires 2026-08-14: outbound client stack, no inbound
# server behind a shared proxy, no patched image published
# And the gate itself, deliberately narrow.
trivy image --scanners vuln --severity CRITICAL,HIGH \
--ignore-unfixed --exit-code 1 --ignorefile /dev/stdin "$image"
Two rules bound what I'm allowed to waive. A critical finding is almost never waived, barring explicit proof of non-exploitability of the kind described above. A high finding can be waived for thirty days at most, the time it takes the publisher to ship.
And one choice I'll defend: --ignore-unfixed. I don't block on vulnerabilities nobody can fix. A gate that screams about problems with no available solution ends up ignored, and an ignored gate protects nothing. What stays on my desk are the flaws with a fix published somewhere that I don't have yet.
The automation doesn't decide for me. It stops me from forgetting what I decided.
A waiver must be as narrow as the decision
This is the detail that costs the most work and the one I'm happiest with.
A waiver applies everywhere by default. You accept a CVE because it's unreachable on one specific service, and you've just made it invisible on every other one, including those where it's perfectly exploitable. The waiver file becomes the blind spot of the whole setup.
So my waivers have a limited scope: a delimited block, tied to an image identified by its digest, and a filter that only lets those lines through when the target being scanned is exactly that image. Everywhere else, the same identifiers mask nothing.
It's the technical translation of a simple sentence: I accepted this risk here, not I accept this risk.
Why a model can't hold that seat
Two reasons, and neither is sampling randomness.
First, it has no state. It can analyze a vulnerability remarkably well, today. It will not come back in thirty days to tell you the waiver it approved has come due. An accepted risk is a debt, and a debt needs a ledger, not an opinion.
Second, its behavior is specified nowhere. People often counter that setting temperature to zero makes it reproducible. That handles sampling variance, not the actual problem: my script only changes the day I change it, whereas a model changes when its vendor decides to ship an update. You can pin a temperature. You can't pin a contract.
What I'm after when I pick an automation over an agent isn't intelligence. It's stability over time.
What this setup costs
It would be dishonest to present it as free, so here are its three flaws, two of which have already cost me time.
Grouped expiry dates produce avalanches. When a whole batch of waivers was written on the same day, it comes due on the same day, and it breaks the delivery pipeline all at once, in the middle of unrelated work. The mechanism is designed to force me back. It doesn't pick the moment, and the moment is rarely good.
Renewing isn't deciding. I have extended a waiver purely because no patched image existed yet. That's not an arbitration, it's an observation, and the only honest way to handle it is to write it as such in the file rather than let it look like a decision. The day renewing becomes a reflex, the setup lies.
Finally, it only covers what declares itself as a version number on an inventoried component. A default-configuration flaw, a bundled library the inventory doesn't list as a product, and the automation stays silent. Perfectly traceable, and perfectly wrong. A deterministic system that's wrong is wrong the same way every time, without ever waking you up. That's the price of reproducibility, and you need to know it to accept it.
Where the model does belong
Exactly where there's nothing to compare, only something to read. A scanner cross-checks versions against a public database. It doesn't read the prose release note of a telecom vendor who fixes a defect without ever publishing a formal advisory. That's text written for humans, and that's the model's seat: turning text into fields, with the right to say it didn't understand. Without that exit, a model handed a list of answers always picks one, and you get a clean, well-formatted, wrong answer.
How much freedom it gets after that depends on its distance from the real action. On a SIP interop diagnosis, I hand it a pcap and it writes a hypothesis, the test to replay, and the full configuration patch, because I'm the one applying it and a wrong patch costs me a review. On a voice agent, it understands the caller's intent and then picks a destination declared in the dial plan, never dialing a number it produced itself. In neither case does it decide to put anything into production.
What's left of it
The question to ask about an automation project isn't whether to put AI in it. It's what still has to be true in thirty days.
A scanner tells you what's true today. A model gives you one reading today, and a different one tomorrow. What saves you the day a customer asks why their equipment stayed exposed is a boring automation that forgot nothing, and a dated, signed decision you can defend.
Notes and sources
- Trivy, ignore file,
--ignore-unfixedand severity scoping: what you choose not to block is part of the policy. - Digest pinning, tracked updates, and a scan as a delivery guardrail: the deployed version is data, not an assumption.
- Constrained model output: forcing fields instead of free text, and leaving it a value that means it doesn't know.
- Two upcoming articles on SIP interop and on draining a datacenter through DNS, which the diagnosis and the hard-block principle are drawn from.
A delivery pipeline to make defensible, a watch that produces noise instead of decisions, a voice agent to bound? Get in touch.
Field note by qaryon
Nicolas Marxer
Enterprise communications solution architect focused on operator, integrator, and B2B deployments.
Need a field view on your communications architecture?
Audit, scoping, or deployment: I work directly on UCaaS, CCaaS, IPBX, SBC, automation, and applied AI.
Discuss a telecom project