Skip to content

hemlock defend monitor

In-band detection middleware. Listens on --listen and reverse-proxies all traffic to --upstream. For every JSON response that includes a response string field, runs the embedded strict-canary detector and:

  • Sets X-Hemlock-Detection: high|medium|low|none on the response
  • Sets X-Hemlock-Specific-Hits: ... when canary substrings matched
  • Inserts a _hemlock verdict block into the response JSON (use --annotate=false to keep responses byte-identical)
  • Appends an audit row to --audit-log if set
  • Replaces high/medium responses with 403 Forbidden + JSON refusal when --block-on-detect is passed

Defaults are non-intrusive: the proxy only annotates. Operators that want enforcement opt in via --block-on-detect.

Synopsis

hemlock defend monitor --upstream <url> [--listen :PORT] [flags]

Flags

Flag Type Default Description
--upstream string (required) URL of the protected pipe (e.g. http://localhost:8100)
--listen string :8200 Bind address for the proxy
--canary-version string v1 Canary registry version used by the embedded detector
--payload-category string (merged) Indicator-list category (default merges across all categories)
--audit-log string JSONL audit log path (default: no audit log)
--block-on-detect bool false Replace high/medium responses with 403 + refusal
--annotate bool true Insert _hemlock verdict block into response JSON
--quiet bool false Suppress per-request stderr logs

Admin endpoints

The proxy serves two endpoints itself (not forwarded to the upstream):

Path Purpose
GET /__hemlock/health Reports upstream + registry pin + block-on-detect setting
GET /__hemlock/stats Lifetime counters: requests, detected, high, blocked

Audit log shape

When --audit-log is set, one JSON object per request is appended:

{
  "ts": "2026-05-06T01:27:09.076569Z",
  "path": "/query",
  "method": "POST",
  "detection": "high",
  "detected": true,
  "specific_hits": ["HEMLOCK_INJECTION_TEST"],
  "generic_hits": null,
  "payload_echoed": false,
  "response_excerpt": "The answer is HEMLOCK_INJECTION_TEST."
}

Examples

Front the local LangChain pipe with annotation only (default), audit log on:

hemlock defend monitor \
  --upstream http://localhost:8100 \
  --listen :8200 \
  --audit-log ./monitor-audit.jsonl

Now query the proxy instead of the pipe directly:

curl -sS -X POST -D - \
  --data-urlencode "q=What is the answer?" \
  --data-urlencode "collection=hemlock_smoke" \
  http://127.0.0.1:8200/query

Headers reveal the detector verdict:

HTTP/1.1 200 OK
Content-Type: application/json
X-Hemlock-Detection: high
X-Hemlock-Specific-Hits: HEMLOCK_INJECTION_TEST
X-Hemlock-Version: dev

Block high/medium-confidence injections instead of just flagging them:

hemlock defend monitor \
  --upstream http://localhost:8100 \
  --block-on-detect

Blocked responses come back as 403 Forbidden with a JSON refusal carrying the verdict:

{ "error": "blocked by hemlock defend monitor", "reason": "injection detected in upstream response", "_hemlock": { ... } }