What Replaces Live Patching When AI Software Can't Phone Home
If your software never talks to the internet again after the day it's installed, what replaces your vulnerability feed? Dependency allow-listing, a software bill of materials, and signed installers, and an honest look at what that trade costs.

Here's a question that only shows up once you've actually tried to ship something that runs permanently disconnected: if your software never talks to the internet again after the day it's installed, what replaces your vulnerability feed?
Most dependency tooling assumes a live connection. A registry you can query at 3am when a CVE drops, a scheduled dependency audit, an auto-patch pipeline that pushes a fix to production before most users have noticed anything happened. All of that is a safety net that catches problems after the fact. Take the network away properly, not "usually connected but pretending otherwise", and that net disappears. Whatever ships in the build is what runs, often for months, with nothing watching it from the outside.
Front-loading trust instead of maintaining it
The practical effect is that disconnected software has to front-load almost all of its supply-chain trust decisions, rather than spreading them across the life of the deployment. In a connected system you can afford to be a bit loose about what gets pulled into a build, because continuous scanning and a patch pipeline sit behind you as a correction mechanism. Offline, there is no correction mechanism. So the question stops being how fast I can react and becomes how sure I am before I ship.
Allow-listing dependencies before they're a problem
The pattern we use turns dependency management from a detection problem into an admission problem. Instead of pulling in whatever the package manager resolves and scanning the result afterwards, we maintain an explicit allow-list of exactly which packages, at which versions, with which hashes, are permitted to enter a build at all. A new dependency doesn't get scanned after the fact, it gets reviewed and added to the manifest before CI will even fetch it. It's a small shift in wording, deny unless approved rather than allow unless flagged, but it changes what a dependency bump actually looks like: a reviewed, auditable diff to a manifest file, not a background process nobody reads.
allowlist = load_yaml("deps.allowlist.yaml")
lockfile = parse_lockfile("package-lock.json")
for pkg in lockfile.packages:
entry = allowlist.get(pkg.name)
if entry is None:
fail(f"{pkg.name} not in allow-list, needs review before it can ship")
if entry.version != pkg.version:
fail(f"{pkg.name} version drift: allowed {entry.version}, got {pkg.version}")
if sha256(pkg.tarball) != entry.hash:
fail(f"{pkg.name} hash mismatch: possible tampering or a silent republish upstream")
build()The SBOM is the record, not the paperwork
Generating a software bill of materials often gets treated as a compliance exercise produced because a customer's procurement team asked for one. For disconnected software it's closer to the only audit trail you will ever have. There's no live inventory service to query later. When a new CVE surfaces for a library bundled eight months ago, the SBOM from that specific release is the only quick way to know which shipped versions actually contain it. We generate it directly from the same lockfile and allow-list manifest CI already checks, so there's no separate process that can quietly drift from what actually got built.
Signed installers instead of a live update check
The other half of a connected system's trust model is usually only accepting updates signed and pushed from its own servers. You can keep the signing, you just lose the pushing. What we use is a release signed with a key kept well away from any build machine, with the public key distributed out of band so it can be checked independently of the installer itself. The verification step is simple and happens once, by whoever is doing the install, often onto a machine with no outbound network path at all.
installer = read_file("release-4.2.bin")
signature = read_file("release-4.2.bin.sig")
pubkey = load_pubkey("release-pubkey.pem")
if not verify_signature(installer, signature, pubkey):
abort("signature verification failed: do not install this binary")The honest tradeoff
None of this is free, and it's worth being straightforward about the cost rather than only talking up the upside. The whole model is a deliberate trade: give up continuous, hours-scale patching in exchange for a system that never calls home. A critical CVE in a dependency on the allow-list can, on a connected system, be patched and auto-pushed in under a day. Here, the same fix has to go through review, a rebuild, a fresh SBOM and re-signing, then physical distribution to machines that might be air-gapped or simply hard to reach. That can stretch to weeks, not hours. That's a real security cost, not a rounding error, and anyone running or recommending this kind of architecture should say so plainly.
It also doesn't remove the need for a human to keep watching upstream security advisories for whatever's on the allow-list. The list isn't a one-time decision, it needs revisiting on a schedule by someone whose job includes checking whether anything on it has picked up a disclosed vulnerability since the last review. The honest version of this setup needs a fast lane too: a shorter, security-only rebuild and re-sign path that skips whatever isn't essential, so when something genuinely urgent turns up, the gap between disclosure and a distributable fix is measured in days rather than whatever the normal release cadence happens to be.