fix(guard): correct PIGuard score semantics and vendor JSON-faithful HTTP gateway - #85
Merged
Merged
Conversation
…HTTP gateway
Two independent bugs caused the PIGuard sidecar to reject virtually every
memory fact with scores pegged at 0.98-0.99, including innocuous content:
1. resultFromResponse treated the sidecar score as an injection probability
(label == INJECTION || score >= threshold). The score is actually the
confidence of the predicted label - a confident BENIGN result scores
~1.0 as well - so the threshold clause rejected almost everything at
the default 0.9. The threshold now only gates INJECTION labels.
2. The docker-compose gateway (upstream examples/http-gateway, a raw-text
demo) wrapped odek's JSON {"text":...} bodies as literal text, so the
model scored the JSON wrapper instead of the content, flipping benign
facts to INJECTION at 0.98-0.99. Replaced with a vendored gateway
(docker/piguard-gateway) that forwards daemon-protocol JSON verbatim
(with raw-text fallback for curl) and builds without amd64-pinned base
digests (Apple Silicon safe).
Verified end-to-end against a live stack: 8/8 benign facts classified
BENIGN, 4/4 real injections classified INJECTION (still rejected), plus
regression tests for the threshold semantics.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
odek | 58e0783 | Commit Preview URL Branch Preview URL |
Jul 21 2026, 09:29 PM |
jkyberneees
added a commit
that referenced
this pull request
Jul 22, 2026
The upstream go-prompt-injection-guard Dockerfile pins its base images by amd64-only sha256 digests, so building the remote git context on Apple Silicon produces x86-64 binaries (piguard-server, libonnxruntime.so) inside an arm64-labeled image and the container crash-loops with "rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2". Vendor the daemon build in docker/piguard-daemon/Dockerfile: it compiles the pinned upstream v1.0.0 source (git clone --branch v1.0.0) with unpinned base image tags, which resolve to the host architecture. The upstream Makefile is arch-aware and selects the aarch64 ONNX Runtime when GOARCH=arm64, so the resulting image contains native binaries (verified: ELF e_machine b7 = EM_AARCH64 for both the server binary and libonnxruntime.so; container reaches healthy and correctly labels BENIGN/INJECTION through the gateway). This replaces the manual Apple Silicon workaround documented in PR #85.
This was referenced Jul 22, 2026
jkyberneees
added a commit
that referenced
this pull request
Jul 22, 2026
… transport (#87) Two follow-ups to the PIGuard false-positive investigation (#85): 1. Scan-rejected atoms no longer vanish. The guard scan in addAtom ran before quarantine routing and hard-dropped rejections, so any guard false positive silently destroyed a legitimate memory with no review path. Rejections now go to quarantine with a recorded reason ("scan_rejected: ..." alongside "tainted"), visible via odek memory extended quarantine. PromoteAtom skips the guard rescan: the human review is the approval, and a rescan would reject the same false positive again. The extractor no longer pre-scans atoms - the single scan gate is at persistence, so extraction and manual adds get identical quarantine-on-reject behavior. 2. guard.socket_path actually works now. The piguard client dialed the Unix socket but spoke HTTP, while the go-prompt-injection-guard daemon speaks newline-delimited JSON - every scan errored and fallback_to_local silently degraded to local-only scanning. Socket mode now forwards the request as one NDJSON line per call (matching the daemon protocol), surfaces {"error":...} daemon replies, and is covered by fake-daemon socket tests plus an E2E run against the real daemon container (BENIGN facts pass, injections rejected, batch/long work).
jkyberneees
added a commit
that referenced
this pull request
Jul 22, 2026
The upstream go-prompt-injection-guard Dockerfile pins its base images by amd64-only sha256 digests, so building the remote git context on Apple Silicon produces x86-64 binaries (piguard-server, libonnxruntime.so) inside an arm64-labeled image and the container crash-loops with "rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2". Vendor the daemon build in docker/piguard-daemon/Dockerfile: it compiles the pinned upstream v1.0.0 source (git clone --branch v1.0.0) with unpinned base image tags, which resolve to the host architecture. The upstream Makefile is arch-aware and selects the aarch64 ONNX Runtime when GOARCH=arm64, so the resulting image contains native binaries (verified: ELF e_machine b7 = EM_AARCH64 for both the server binary and libonnxruntime.so; container reaches healthy and correctly labels BENIGN/INJECTION through the gateway). This replaces the manual Apple Silicon workaround documented in PR #85.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Reported against the Extended Memory flow with
docker/config.restricted.json: the memory injection detector rejected every proposed fact — including innocuous ones — with scores pegged at 0.98–0.99.Reproduced live against the actual PiGuard compose stack. Root cause is two independent bugs, each alone sufficient to reject everything (not the session taint system, which works differently and produces no scores).
Bug 1 — threshold applied to the wrong quantity (
internal/guard/piguard.go)resultFromResponsetreated the sidecar score as an injection probability:The PIGuard score is the confidence of the predicted label, whichever label that is — a confident
BENIGNresult also scores ~1.0 (confirmed against upstream README and live responses:"The user prefers tea over coffee." → BENIGN, score 0.99998). Withthreshold: 0.9, theScore >= thresholdclause rejected virtually every response. The threshold now only gatesINJECTIONlabels:Regression coverage: the fake server in
piguard_test.gonow returns realistic high-confidence BENIGN scores (0.9997 — the old 0.1 masked the bug), plus a newTestResultFromResponse_ThresholdSemanticstable test.Bug 2 — gateway double-encodes odek's JSON (
docker/)The compose stack bridges odek → daemon via the upstream
examples/http-gateway, which is a raw-text demo: it wraps the entire request body as the text. odek's client POSTs JSON{"text": "..."}, so the model scored the JSON wrapper instead of the content. Live results with the old gateway:"Deployments happen every Friday at 5pm." → INJECTION 0.992,"The user prefers dark mode in their IDE." → INJECTION 0.984.Fix: vendored gateway
docker/piguard-gateway/that forwards daemon-protocol JSON (text/long/texts) verbatim to the Unix socket (raw-text fallback kept for curl).docker-compose.ymlnow builds it locally; its Dockerfile drops the amd64-pinned base digests so it builds natively on Apple Silicon (the upstream-pinned digests produce x86-64 binaries inside arm64-labeled images —rosetta errorcrash — a third, build-time issue documented in compose comments).E2E verification (live stack, odek's exact JSON payloads)
/healthz, raw-text mode, and batch/rawverified working.go vetclean;go test ./internal/guard/... ./internal/memory/...all green.Notes
docker/piguard/.gitignore: ignoremodeling_*.py, an artifact the model export drops intomodels/.piguard:local.