This lesson needs macOS: it uses the built-in
sandbox-exec tool to deny network access.
On Linux or Windows, see How to run Redact offline instead.redact-10-offline, after redact-09-evaluation
What does “offline” actually prove?
Unplugging the Wi-Fi is a weak test. Something could quietly wait for the network to come back, or you might forget to unplug it. A stronger test is to make the operating system refuse every outside connection for this one process, and watch it succeed anyway. That’s what macOS’ssandbox-exec does.
It runs a command under a small policy, and ours says: allow everything, except network connections to anywhere that isn’t this computer.
If Redact tried to call a server, the call would fail, and so would the run.
Be clear about the limits.
This proves what the Redact service could reach while it ran.
It doesn’t prove the suggestions caught every private field, and it doesn’t cover the first run, which needs the internet once to download the models.
More on this: About model downloads, caching and offline use.
1. Get this lesson’s code
Stop the app and the service if they’re running. From the repository root,redact-course, bring in this lesson’s commit:
2. Read the sandbox policy
Openpackage.json and find qvac:offline:
(allow default) lets the process do everything as normal.
(deny network-outbound …) then refuses any outgoing TCP or UDP connection whose destination is not localhost.
qvac:service:offline wraps the normal service in the same policy.
3. Read the proof script
Openqvac-service/offline-cli.ts.
Before loading anything, it checks the models are already on disk:
assertWarmedCache in offline-proof.ts looks for the three model files by name:
withRemoteFetchBlocked, which makes any http: or https: fetch fail while the analysis runs.
It prints how many blocks OCR found, which labels Laya suggested, and the timings.
4. Make sure the models are cached
craft, one containing latin, and one ending laya-Q8_0.gguf.
If any is missing, run one analysis in the app with the network on first.
5. Run one analysis with the network denied
With the service stopped:offline-receipt.json.
You should see:
run there’s "status": "ready", "reasoningModel": "laya-421m-q8" and the timings.
In our run OCR took about 16 seconds and Laya about 1.3 seconds, including loading.
Those seven suggestions are the same ones you saw in the app, produced while the process couldn’t reach anything beyond this computer.
6. Run the whole app with a sandboxed service
Build the app once:7. Run the browser receipt
In a third terminal, insideredact, install Playwright’s browser once:
1 passed.
In that time the test drove the real app the way you would:
- It uploaded the chat, chose Private and typed a direction.
- It waited for local suggestions, accepted the name, rejected the email and accepted the rest.
- It added one manual mark, selected Done and saved the copy.
- It checked a pixel inside the name is black in the saved file, and that the source file’s SHA-256 didn’t change.
- It checked the browser made no request to any host other than
localhostor127.0.0.1.
REDACT_LIVE_QVAC=1 the test is skipped, not passed.
If something goes wrong
QVAC cache is incomplete: a model file is missing; run one analysis in the app with the network on, then try again.Process sandbox did not block remote networking: you ran the script withoutsandbox-exec; usenpm run qvac:offline, nottsxdirectly.Executable doesn't existfrom Playwright: runnpx playwright install chromiumonce.- The browser test times out: make sure
qvac:service:offlineandnpm run startare both running, and that nothing else is using port 3101.
8. Check your work
Stop the app and the service, then:npm test ends with Tests 85 passed (85).
Keep offline-receipt.json outside the app, or delete it.
So what just happened?
Redact worked end to end with the operating system refusing every outside connection. Local OCR and Laya proposed, a person decided, and a new flattened copy was saved, all inside the machine. You now have two receipts: one for the analysis, one for the whole app.Try it yourself
Rename the Laya file in.qvac/models to something without laya in it, run npm run qvac:offline, and read the error.
Then rename it back.
That’s the cache check protecting you from an offline run that could never work.
Where to next
- Every file in this checkpoint:
redact/atredact-10-offline - About model downloads, caching and offline use and About what Redact does not protect