CPU-only HTTP service for two PII models in a Tinfoil confidential-compute enclave:
screenpipe/pii-redactor(v50_distilled6l), the same hash-verified text checkpoint and vocabulary remap as the desktop app. EndpointPOST /filter.screenpipe/pii-image-redactor(rfdetr_v38), with the existing whole-frame plus four-tile screenshot policy. EndpointPOST /image/detect.
tinfoil-config.yml provisions 8 vCPUs, 16 GB RAM, and no GPU. Dockerfile.cpu includes ONNX Runtime CPU and neither generative-model weights nor CUDA. PRIVACY_ONLY=true skips both chat upstreams and makes readiness require both privacy models on CPU. The public shim exposes only the existing PII endpoints and health. Previously hosted Gemma and GLM routes are retired.
The legacy GPU Dockerfile, entrypoint and release history remain available for rollback; their sizing notes below are historical.
GET /health → {"status": "ok", "model_ready": true, "image_model_ready": true,
"pii_gpu_ready": false, "text_provider": "CPUExecutionProvider", ...}
POST /filter → {"text": "My email is alice@foo.com"}
← {"redacted": "My email is [EMAIL]",
"spans": [{"label": "private_email", "start": 12, "end": 25,
"text": "alice@foo.com", "score": 0.99}],
"latency_ms": 180,
"model": "screenpipe/pii-redactor:v50_distilled6l (mixed-int4-int8-onnx)"}
POST /image/detect → {"image_b64": "<b64-jpg-or-png>", "threshold": 0.30}
← {"detections": [{"bbox": [x, y, w, h], "label": "private_person", "score": 0.95},
{"bbox": [x, y, w, h], "label": "secret", "score": 0.91}],
"latency_ms": 95, "model": "rfdetr_v38",
"width": 2880, "height": 1800}
Bbox is [x, y, w, h] in original-image pixel space (the server un-resizes from the model's 512×512 input). Labels are the canonical 12-class screenpipe PII taxonomy.
# build
docker build -f Dockerfile.cpu -t privacy-filter:dev .
# run
docker run --rm -p 8080:8080 privacy-filter:dev
# smoke test
curl -s http://localhost:8080/health
curl -s -X POST http://localhost:8080/filter \
-H 'Content-Type: application/json' \
-d '{"text":"Call Alice at +1 415 555 0100 about alice@example.com"}' | jq
# Image: send a JPG/PNG as base64.
B64=$(base64 -i some_screenshot.png)
curl -s -X POST http://localhost:8080/image/detect \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg img "$B64" '{image_b64: $img, threshold: 0.30}')" | jqThe first build downloads the v50 text contract (~133 MB across model, tokenizer, config, and vocabulary remap), the 60 MB rfdetr_v38 ONNX. Subsequent builds use the build cache. Both HuggingFace revisions and every model artifact checksum are pinned, so a rebuild cannot silently drift to different weights.
-
Build and push the image to GitHub Container Registry using the
privacy-filter releaseworkflow. Supply the semantic version without thevprefix (for example0.10.0-cpu.rc1) and selectcpu_only=true. The workflow prints the immutable CPU image digest. The build runs actual text and image inference before uploading.gh workflow run release.yml --ref <branch> -f version=0.10.0-cpu.rc1 -f cpu_only=true
-
Pin the digest in
tinfoil-config.yml, commit it, then create the matchingv*tag. The tag runs the Tinfoil measurement/attestation job.git add tinfoil-config.yml git commit -m "release: CPU privacy-filter v0.10.0-cpu.rc1" git tag v0.10.0-cpu.rc1 && git push origin <branch> v0.10.0-cpu.rc1
-
Update the existing deployment through Tinfoil's blue-green update flow. Stage the candidate when you want to exercise it before promotion; the current instance stays live unless the candidate becomes healthy and is accepted.
-
Verification and client trust selection happen automatically. Every measurement build creates a prerelease, leaving clients on the deployed build.
Verify live Tinfoil releasechecks after builds and on GitHub's five-minute schedule: exact-tag Sigstore provenance, AMD hardware attestation, TLS binding, and a synthetic PII request must all pass before it selects the deployed release as GitHub latest. No separate release edit or publishing click is required. Signed release-version order prevents an automatic rollback to arbitrary historical signed code; the initial repair is pinned to the exact known release IDs and verified manifest digest.A deployment can pause privacy processing until that automated check runs; GitHub schedules can be delayed. Build-only candidates do not change client trust. Failed checks leave selection unchanged and surface in Actions; subsequent scheduled runs retry. Existing required-PII clients retain their pending recordings during the pause. The read-only diagnostic is
python3 scripts/reconcile_tinfoil_release.pyafter buildingtools/tinfoil-release-check;--applyis reserved for the workflow.
Text model (v50_distilled6l) — mixed int4/int8 ONNX:
| Metric | Value |
|---|---|
| Model artifact | 114 MB |
| Model shape | six-layer XLM-R student |
| Runtime window | 256 tokens with 64-token overlap |
| Local CPU reference | 7–9 ms for short captured strings |
Image model (rfdetr_v38) — CUDA EP:
| Metric | Value |
|---|---|
| Weights (FP16, FP32 I/O) | 60 MB |
| Params | ~25 M |
| Input resolution | 512×512 |
| Inference policy | whole frame + four overlapping tiles for large desktops |
| Real captured-frame core recall | 81.2% for email/phone/secret with tiling (62.5% whole-frame only) |
| Real-screen false-positive audit | 0 clear false positives across the held-out 240-frame set at 0.50 |
The deployed CVM also carries Gemma E2B and its vLLM working set, so
tinfoil-config.yml remains the source of truth for total CPU, memory, and GPU sizing.
GLM-5.3-Flash REAP50 (IQ3_M) — llama.cpp/CUDA:
| Metric | Value |
|---|---|
| Quantized model | 67.2 GiB |
| Parameters after expert pruning | about 165B (from 321B) |
| Runtime context | 65,536 tokens total, two 32,768-token parallel slots |
| GPU offload | all layers on the H200 |
| Prefill batches | 4,096 logical / 1,024 physical tokens |
| Decode acceleration | ngram-mod self-speculation (16-token match, 4–32-token drafts) |
The model and runtime are pinned by exact commits. Tinfoil mounts the measured model package read-only; the model is not downloaded at runtime.
- Tinfoil remote attestation covers the exact image digest, so clients can verify the specific model bits + server code that handled their request.
- Only the paths listed in
tinfoil-config.ymlare exposed — Tinfoil's shim allowlist blocks every other URL at the enclave boundary. - Model weights are immutable. The text, image, and Gemma artifacts are baked into the router image. GLM is a measured Tinfoil model package mounted read-only. No workload downloads weights at runtime.
- The exact container image is measured and attested. Tinfoil runs the router and co-hosted vLLM as the artifact-owning
appuser(10001:999) with access to the injected NVIDIA device; the shim and CVM remain the external security boundary.
- Text: English-primary. Multilingual coverage per upstream model card varies.
- Text: long inputs are split into overlapping 256-token windows; request size is capped separately by
MAX_INPUT_CHARS. - Image: v38 materially improves measured real-screen precision and tiled recall, but unusual layouts, scripts, handwriting, occlusion, and adversarial inputs still need their own evaluation.
- Image: payload cap
MAX_IMAGE_BYTES=20 MB(override via env). Decoded RGB working buffer is ~3× the payload. - GLM: the REAP50
IQ3_Mbuild trades quality for fitting beside the existing workloads on one H200. Validate it on your own tasks before replacing a larger hosted model. - Not a compliance certification. One layer in a privacy-by-design stack.
PolyForm Noncommercial License 1.0.0. You can read, run, fork, modify, and share — noncommercial use only. Commercial use requires a separate license; reach out to louis@screenpi.pe.
Bundled / downloaded model weights keep their own licenses: screenpipe/pii-redactor, screenpipe/pii-image-redactor (rfdetr_v38), google/gemma-4-E2B-it (Gemma Terms of Use), and patrickbdevaney/GLM-5.3-Flash-REAP50-GGUF (MIT). Using this deployment commercially means complying with all of them in addition to this repo's license.