[3.6.0] - 2026-10-01
Breaking Changes / Upgrade Notes
- Building from source requires Go 1.26 or newer. Go 1.25 no longer receives upstream security fixes and current
golang.org/xmodules require 1.26. Release binaries are built with Go 1.26.8, the same toolchain the vulnerability scan checks, and CI tests Go 1.26 and 1.27. (#1670, #1673) - A config file that omits
fetch_proxy.monitoring.blocklistnow gets the shipped blocklist. Before this only the no-config path carried it, so a config file without the key (including the Helm chart's default values) ran with an empty blocklist. Destinations on the shipped list that were reachable under 3.5.0 will now block. Setblocklist: []to keep it off. (#1655) - Request-body trust has its own list. On a destination in
request_body_scanning.trusted_hosts, request-body injection and a fully redacted critical credential follow the configured action instead of the hard block. The request side no longer readsresponse_scanning.exempt_domains, so an operator who relied on a response exemption to relax outbound bodies must add the host totrusted_hosts. (#1454, #1637) - Host patterns are validated against how they're matched. Allow, deny, trust and bypass lists refuse a pattern that isn't an ASCII hostname before case folding, that carries a URL,
host:port, fragment, interior wildcard or malformed label, or whose validated form differs from what the matcher compares. A destination with an empty DNS label (an extra trailing dot) is refused as invalid on every transport. (#1524, #1541, #1548, #1569) tls_interception.passthrough_domainsrefuses a wildcard over a public suffix, including private-section suffixes shared by many tenants, because passthrough turns off body and response scanning for everything under it. List exact hosts or intercept with a trusted local CA. (#1595)- Temporary exceptions have a maximum expiry. Seven controls have horizons enforced at load and reload:
request_body_scanning.sigv4_credential_routes[].expiresandresponse_scanning.core_observe_exceptions[].expires(30 days),sandbox.best_effort_expiry(30 days),request_body_scanning.content_entropy_warn_routes[].expiresandresponse_scanning.unscannable_passthrough[].expires(90 days), andfetch_proxy.monitoring.path_entropy_exclusions[].expiresandfetch_proxy.monitoring.query_entropy_param_exclusions[].expires(180 days). The refusal names the field, date and maximum. A config-sourced sandbox expiry must be an absolute RFC3339 time; best-effort reason and expiry must come from one source, and per-agent sandbox overrides need their own reason and expiry within the same 30-day limit. (#1447, #1520, #1600) session_profiling.volume_spike_ratiois removed. Nothing ever read it. A config that still sets it, top-level or per agent, is refused at load with a message naming the key. The canonical policy hash changes because the config shape changed; enforcement doesn't. (#1527)- A custom DLP pattern that reuses a core pattern name can't carry
exempt_domains. Config validation refuses it at startup and reload instead of accepting an exemption the core floor never honored. (#1451) pipelock runrefusesfile_sentry.action: blockon the server listener. That listener has no child process to stop, so block could only log. A reload asking for it is rejected atomically. Subprocess MCP mode andaction: warnare unchanged. (#1452)- Named agent policy follows bound identity only (Enterprise). A per-agent listener or
source_cidrsmatch selects the named profile. A self-declaredX-Pipelock-Agentname is still recorded for attribution but gets the fallback policy, and its session state folds to the client IP so rotating names can't reset adaptive scoring. (#1461, #1530, #1578) contain installrefuses to swap in a core build that can't enforce a named-agent config. Its pre-installcheckof the candidate binary requires the build to enforce every configured capability, so a working enterprise binary isn't replaced by one that can't enforce its listeners. A plainpipelock checkon a core build still validates a named-agent config and exits 0. (#1629)- Each Pipelock process writes its own receipt chain. Processes sharing one flight-recorder directory no longer fork a chain; a restart is recorded as a signed link naming the tail it continues.
verify-receipt --chainandevidence doctorfollow the links. Tools that read a single session file must read the run chains instead. (#1654) - Activating the kill switch blocks held MCP approvals that haven't started sending. A held call that previously forwarded after activation now resolves as blocked. (#1699)
- The default canonical policy hash changes because a signed GitHub release-download JWT may now reach
release-assets.githubusercontent.comin anhttpsURL query, and nowhere else. Receipt consumers that pin the default hash will see a new value. (#1730) - An approved MCP call that hasn't been sent yet is cancelled if the kill switch activates first. The client gets the same
-32004error as any other killed call, and the journal records a block with sourcekill_switchwhile the signed receipt records layerkill_switch. A config reload that turns the switch on applies it before the new proxy policy takes effect, including when the configured sentinel file already exists. Tools that group receipts by layer will see the newkill_switchvalue. (#1728) - A held HTTP call that the upstream gate refuses at release records resolution source
upstream_contract. Readers of deferred-resolution records will see this new source. (#1728) - GitLab
Private-TokenandJob-Tokenheaders are scanned for credentials by default. GitLab personal access tokens and CI job tokens sent to their issuer are still allowed; for a self-managed GitLab, list the host indlp.gitlab_hostsso those tokens count as going to their issuer. Other GitLab token types stay blocked. (#1728) - Reading an SSH public key (
id_*.pubunder.ssh) no longer trips the built-in Credential File Access rule when the path is the call's only string value, on the MCP proxy,pipelock explain, the Scan API and hook events, including~/.sshspellings and keys that exist on the host. Shell commands, calls with another string value, any JSON key that names a protected path, and arguments nested too deep to inspect stay blocked. A custom rule that reuses the built-in name keeps the ordinary matcher. (#1726, #1734) - When MCP receipts are required, a request forwards only after its receipt is durably written. A failed or unsupported receipt write now blocks the request. (#1696)
audit-packet --offlineno longer reports a packet as trusted. The Go, Rust and TypeScript verifiers returnverdict: schema_checked_trust_unverifiedwithtrustedandvalidfalse and exit non-zero. Separately,pipelock verify-receipt --clean-reportoutput carries a requiredverification_mode(pinned_provenanceorunpinned_structural). CI jobs that accepted the old offline success must require chain verification with an explicit trust anchor. (#1700)- The Scan API can return
decision: "warn"for atool_callthat matches a warn-configured tool-policy rule, matching what the live MCP proxy does. Consumers that expected onlyallowordenymust handlewarn. (#1572) - Shipped presets now set
arg_source: patch_targetson tool-policy rules, and a binary older than 3.6.0 refuses to load them. The field is unknown to earlier releases, so copying a 3.6.0 preset onto an older binary, or sending a conductor bundle that uses it to an older follower, fails at config load instead of running without the rule. Upgrade the binary first, or keep older followers on bundles without the field. (#1557) - Contained hosts need
pipelock contain installwith the new binary to adopt the private network namespace, private temporary directories, the0600config mode and the refreshed CA export. (#1558, #1624, #1640, #1677) - The absolute-URI forward proxy hands an allowed redirect back to the client instead of following it. A client that follows redirects, such as a browser using
HTTP_PROXY, now sends each hop as its own request through Pipelock, and each hop gets full admission, a new receipt and its own mediation envelope, with cookies staying at the issuing origin. A redirect that targets something Pipelock would refuse is still refused before the response is released, and the redirect check no longer spends rate-limit capacity for a request nobody sent. A 301, 302, 303, 307 or 308 whoseLocationis duplicated or carries a backslash, whitespace or a control character is blocked with a 403 and the reasonambiguous redirect location./fetchstill follows and rescans redirects itself. (#1749) - The
redirectaudit event now saysredirect observed, notredirect followed. It fires when Pipelock sees a redirect, before it admits the target, and it isn't proof the target was contacted. Anything matching on the old message text needs the new one. (#1749) - With
flight_recorder.require_receipts, a credential-audience, issuer-cookie or issuer-query allow forwards only after its receipt is durably written. A failed write blocks the request with reasonreceipt_emission_failedand layercredential_audience_receiptorissuer_allow_receipt. Withrequire_receiptsoff these receipts stay best-effort. The receipt takes its policy hash from the config the request started under, not one a reload installed mid-request. (#1741, #1744, #1745)
Added
- Contained agents run in their own network namespace. The agent reaches Pipelock and declared host services only through doorway sockets Pipelock forwards. The namespace is verified at launch, survives a binary swap and is restored on rollback.
containment.published_servicespublishes agent services to the operator, andcontainment.displayprovisions a screen inside the namespace for a contained browser. (#1677) pipelock contain view [--control]lets an operator watch or take over a contained agent's screen with any VNC client through a caller-only local socket. A view-only session can't type or click, and at most one controller holds the display.containment.display.geometrysets the screen size. (#1691)- Declared loopback services for contained agents.
containment.loopback_servicesnames each extra local destination with host, port, owner, reason and expiry. Replies are allowed only on the reply path, a privileged timer removes an entry when it expires, andcontain verifyfails when either half of a pair is missing or misordered. A contained agent can also reach its own subprocess on any loopback port while unrelated host listeners stay unreachable. (#1603, #1610, #1618, #1625) pipelock contain runprints a session contract before launch listing the agent user, egress posture, capsule path, private/tmpstate, tools and workspace grants with expiry.--dry-runruns preflight and prints it without launching, and expired grants refuse. (#1536)- A signed workspace change statement after each contained session, bound to the posture capsule, listing files the agent created, modified or deleted.
pipelock posture verify --workspace-statementchecks the pair and fails an incomplete statement with exit 2. (#1601) - Contained agents get private
/tmpand/var/tmpthrough a transient systemd service, verified before launch. (#1558) - Browser CA trust for contained agents.
contain installadds the interception CA to the agent's Chromium certificate database, verification reports it as probe 20, and rollback removes only what install added. (#1619) systemctl reload pipelockwaits for the verdict on systemd 253 and newer. The unit isType=notify-reloadand reports whether a reload was applied or rejected insystemctl status. (#1562)pipelock contain doctorgains checks 7 and 8. Check 7 reads the managed chain structure on its own; check 8 checks that every managed doorway socket (the proxy doorway plus one per declared loopback service) is enabled and active. A definite agent bypass rule now reports FAIL instead of UNKNOWN. (#1560, #1659)- Credential audiences. Built-in provider keys, the chat-platform bot token, Slack tokens, Google OAuth bearer tokens and GitHub and GitLab tokens are allowed when sent to the API authority that issued them and blocked everywhere else, with an audited allow and receipt. The audience sets are compiled in and can't be set from YAML.
dlp.github_enterprise_hostsanddlp.gitlab_hostsname exact self-hosted instances. (#1523, #1644, #1657, #1672) - AWS Signature Version 4 requests reach their own AWS endpoint. The Authorization header envelope and presigned-URL credential scopes are parsed to the shape AWS publishes, shared between the scanner and the redactor, and
request_body_scanning.sigv4_credential_routesnow applies on the reverse proxy too. (#1453, #1529, #1551, #1561) - Issuer-bound session cookies. With TLS interception on, a cookie is left out of header DLP only when the same session received that exact name and value from the same host over HTTPS and returns it within its domain, path and expiry.
request_body_scanning.issuer_bound_session_cookiesdefaults totrue. (#1676) - Issued paging tokens pass the query entropy gate. A query value that an intercepted JSON response from the same host handed out, such as a next-page link, isn't blocked by query entropy when sent back to that host in the same session. Search text is scored word by word. DLP still runs. (#1689)
response_scanning.core_observe_exceptionsobserves one core response pattern on one exact host with a reason, owner and expiry of at most 30 days. The pattern still runs and the finding is kept under its owncore_observedreason; only the block is withheld. (#1620)fetch_proxy.monitoring.path_entropy_exclusionsexempts one host plus one literal path prefix from the path entropy gate only, and ships defaults for ordinary document-sharing links. Operator entries add to the shipped routes. (#1522, #1539, #1671)- Scoped block rules can exempt one exact JSON string value for a specified request. Missing, unreadable, ambiguous and other values stay blocked, including inside batch requests. (#1692)
mcp_tool_scanning.new_tool_admission(admitorwithhold) governs a tool name that appears after the drift baseline is established.withholdkeeps it out of the baseline until a signed listener drift reset. (#1584, #1588, #1598)pipelock explain responsereads a saved HTTP response body on stdin and reports which response pattern matched, in which scanner view, at which offset, with digests instead of the matched text. Response explanations also cover recorded findings and A2A response policy. (#1576, #1613)pipelock verify-receipt --whole-recorderverifies every recorder entry, the receipt chain and the clean-shutdown transcript seal, and says which mode it ran. (#1573)X-Pipelock-Receiptresponse header carries theaction_idof the signed receipt that covers a response, set only after the receipt is recorded. Blocks set it on every transport; allows set it underflight_recorder.require_receipts. (#1486, #1604)X-Pipelock-Shield-Rewriteresponse header lists the categories Browser Shield rewrote, and the fetch envelope carries the same value asshield_rewrite. (#1455)- Browser Shield can deliver SVG in sanitized form after the complete decoded body and its rewrite pass structural validation. Anything else refuses, and SVG stays refused while Shield is off. (#1661)
- Scan API
context.session_idaccumulates across requests with the same bearer token, so a secret split acrossdlp,prompt_injectionortool_callrequests is caught on the request that completes it. (#1582) - DNS-over-HTTPS is inspected as DNS. A strict RFC 8484 GET or POST message is checked by DLP and the entropy gate on every name, record payload and option. (#1683)
- Partial disclosure of a known secret is detected. Eligible canary, environment and file values match contiguous disclosures of 16 bytes or more in plain or decoded text. Whole values spelled as decimal character codes also match; for environment and file secrets, decimal character-code matching requires the whole value. (#1494)
- URL destinations inside query parameters are evaluated with the same allowlist, blocklist and SSRF checks as the outer host, through raw, percent, hex, base64 and base32 layers. (#1435)
- Failed bearer guesses are rate limited per client address on the kill-switch API, Scan API, session admin API and MCP HTTP listener: ten wrong credentials in a minute get 429 with
Retry-After. (#1555) - Integrations for Pi and Continue.dev.
pipelock pi installconfigures a named proxy listener, andpipelock continue installwraps every MCP server Continue declares, both withremoveand--dry-run. A Grok CLI guide covers forward-proxy and MCP wrapping. (#1476, #1487, #1589, #1602) pipelock hermes installsets browser launch defaults that stop agent-browser advertising the automation marker, reported byhermes verifyand reversed byhermes rollback.--no-browser-defaultsskips it. (#1662)pipelock doctorchecks file-sentry coverage by walking the configured roots without installing watches. (#1498)pipelock init --no-auditorskips the evidence auditor timer, and init now discloses the timer before installing it. (#1593)- Released Helm charts carry build provenance bound to the pushed digest, verifiable with
gh attestation verify. (#1685) - License service: a 60-day self-serve Enterprise trial tier, operator commands to inspect, resend and revoke trial access, a provider-backed
/readyendpoint, a customer self-serve resend endpoint that is off unless enabled, andlicense-service audit-summary. Provider API calls pin a dated version. (#1456, #1538, #1599, #1631, #1678, #1690) pipelock contain run --lifecycle-output DIRwrites alifecycle.jsonreport for the transient systemd service that runs the agent, bound to the unit's invocation ID, the launch arguments' digest, the config and policy hashes and the posture capsule digest. It records whether the service and its cgroup stopped, so automation doesn't have to treat a stoppedsystemd-runclient as proof. It needs a new root-private directory, Linux with cgroup v2 andbusctl, and an actual launch (--dry-runrefuses it). Argument values, environment values and key material aren't written. (#1749)- Secret-egress evidence has a fixture-only contract. It defines typed decisions with a retained or redacted destination, an independent coverage model and a signed receipt payload kind,
secret_egress_decision_v1, registered as fixture-only with a closed wire profile that the Go receipt parser andpipelock-verifierenforce. No Pipelock transport emits it yet. (#1737, #1740) - Containment conformance fixtures can supply nft chain text that runs through the same recognizer
contain verifyuses. (#1581) whoamireports identity provenance (bound, self-declared or unknown) and resolves identity the way proxied traffic does. (#1591)
Changed
- Release binaries are built on a Go toolchain with no reachable known advisories. (#1473)
- Reverse-proxy traffic joins session enforcement. Session profiling, adaptive enforcement, cross-request entropy and taint apply to it, source CIDR bindings attribute it, and reverse DLP denials emit signed receipts. (#1477, #1532)
- Listener and CIDR identities are graded
bound, so audit, OCSF and CEF fields name them correctly. Bound identities no longer fill the shared per-IP burst bucket. (#1461, #1483) - Identical blocked retries add threat score once, and destination-scoped airlock transitions apply consistently and survive recorder replacement.
session resetclears destination-scoped state. (#1531, #1534, #1607) - Response scanning skips patterns that can't match and runs the rest in parallel, without changing what it detects, and caches a clean verdict for an identical body per pattern set. Scans also share decoded and normalized views of the same input, and a response scan reuses matches it already found on that body, with findings and their order unchanged. There is no 3.5.0 baseline for a speed claim, and an identical large body is not served faster on the reverse proxy, so this entry makes none. (#1668, #1580, #1746, #1749)
- Response injection patterns for system prompts, persistence and credential concealment need directive phrasing, so descriptive tool output passes while direct instructions still block. The core injection regex is one constant shared by the core floor, the defaults and every preset. (#1446, #1609)
- Browser Shield reads HTML the way a browser does. It uses the HTML tokenizer, reads inline
stylewith the CSS Syntax Level 3 algorithm, leaves JavaScript byte for byte under every JavaScript media type RFC 9239 lists, keeps interface markup in hidden application views, and stops breaking pages and bot checks. An intervention it can't record is refused. (#1570, #1616, #1641, #1666, #1682) - Response bodies are classified from their bytes, so image pixels and opaque binary data don't match prose-only detections while image metadata, embedded text and UTF-16 text keep being scanned. (#1634)
- Compressed responses are decoded and scanned instead of refused for gzip and deflate, every
Content-Encodingvalue is read, and an encoding with no decoder still fails closed. (#1642, #1650) - The fetch hidden-content surface is built from an HTML parse tree, and executable JavaScript bodies are no longer scanned as hidden page text. (#1623)
- Block reasons name the finding that blocked. Query entropy blocks carry
query_entropy, a body block names entropy only when entropy's own action blocked, rate-limit and data-budget blocks name the shared base domain, and scanner errors are reported as errors instead of injection detections. (#1475, #1638, #1663, #1667) - Audit mode observes request-body prompt injection. With
enforce: falseit followsrequest_body_scanning.action, and each finding is reported once. (#1681) - The core injection scanner runs when
response_scanning.enabledis false on reverse-proxy response bodies and pre-action hook payloads. Pre-action hooks scan submitted commands, tool arguments and supplied file content; they don't intercept the later tool response. (#1628) - A2A agent-card drift judges what a change introduced. Structural changes still block; a descriptive change blocks only when it introduces a new cue class. (#1537, #1633)
- Tool policy recognizes namespaced tool names and terminal execution in chain detection. (#1571)
- Hot-reload refusals name the field that would have weakened a required mode and say a restart applies it. Rejected trust expansion is explicit in diagnostics. (#1478, #1652)
- Host sets are canonicalized (case, duplicates, trailing dots, IDNA) before policy hashing and matching. (#1460, #1569)
- Conductor followers require a verifiable version before applying a bundle with a minimum version, restore and re-verify cached policy before serving, keep live and durable policy consistent, and bind rollback authorizations to their target stream without resetting the replay counter. (#1471, #1627, #1630, #1693)
- Receipt resume across a key change trusts only a key this process loaded earlier in the same run. Final checkpoints survive shard rotation, and non-Rekor anchor proofs are verified before they're persisted. (#1535, #1577, #1687)
- The external action grant verifier's contract now requires
not_beforeand caps a grant's lifetime at five minutes, a Pipelock limit informed by the short token lifetimes the OAuth Transaction Tokens BCP recommends, and refuses withnot_yet_validorlifetime_exceeded. The signed references it checks may declarejcs-rfc8785-nfccanonicalization. No shipped configuration enables the verifier yet. (#1575, #1617) - MCP startup is recorded. Initialization and tool-list requests and the initialized notification record correlated outcomes, and a required recording failure stops forwarding. (#1467)
- Emitter and SIEM health snapshots publish counters, degraded state and error details together. (#1481, #1484)
- Every dropped DLP finding is recorded in
pipelock_dlp_dropped_matches_totaland adlp_warnaudit line. (#1458) - Containment probes that can't establish a cause report UNKNOWN with a non-zero exit, Podman is recognized, and the managed nftables ruleset is reconciled instead of appended. (#1466, #1507, #1621)
- The dashboard requires exactly one
Authorizationheader, and an embedder that disables its own auth must name the outer boundary. (#1474, #1553, #1590) - Setup integrations parse Codex warnings separately, find packaged Claude Desktop on Windows, preserve runtime-resolved VS Code configuration, replace recoverable older wrappers, and report bundle-load problems from every hook. (#1465, #1546, #1554, #1568)
pipelock hermes installandrollbacktake an exclusive lock so two runs can't interleave. (#1665)- File-sentry startup failures name every unreachable subtree and summarize the remedies first. (#1498, #1545)
pipelock initwaits briefly for user systemd before skipping the evidence auditor. (#1626)- A rules bundle fetch blocked by Pipelock says so and names the reason. A bundle whose
min_pipelockcan't be checked on a development build warns and loads. (#1447, #1540) - The sandbox bridge closes relays idle in both directions after the largest configured proxy idle bound. (#1701)
- The sandbox launches on busy desktops: the shared-UID task ceiling rose from 4,096 to 8,192, a launch adds at most 1,024 tasks above the current count, a stricter inherited limit still wins, and a launch at the ceiling is refused. (#1707)
contain installchecks its prerequisites before it changes the host. Preflight now looks forcertutiland for an agent tool to allow-list, and judges whetherpipelock-agentcan run that tool by walking its path the way the kernel does, including symlinks,.and... Before, the install failed at a late step and rolled back everything applied up to it. A reinstall keeps an existingadd-toolallow-list when no default tool resolves, and an allow-list file thatpipelock-agentcan't read is repaired. (#1723, #1725)
Fixed
-
The contained systemd service starts again.
contain service-postureruns its host-side checks from the host network namespace, where the managed nftables table is visible, so the documentedExecStartPrerecipe works.contain runno longer requires a published listener before the agent that creates it has started. (#1759, #1770) -
An expired loopback or published service entry no longer takes the proxy down. An expired
containment.loopback_servicesorcontainment.published_servicesentry is dropped and reported at load, startup, reload, install and nft reconciliation instead of refusing the whole configuration. Other entries keep working, a malformed entry still rejects, andcontain verifystill fails and names the expired entry. Retiring a forwarder also stops the relay its socket started, and cleanup only touches units whose definitions match what Pipelock installed. (#1759) -
contain doctorchecks the in-namespace forwarder services, and doctor and verify give remedies that work when a unit is masked or a relay still holds the port. (#1759) -
A promoted Learn and Lock manifest applies live in
pipelock mcp proxyandpipelock runwithout a restart. A rejected promote keeps the last accepted contract and is logged. (#1759) -
pipelock initchecks the saved or retained configuration, and prints follow-up commands with that exact config path. Malformed retained files no longer pass checks against an unused preset; dry runs continue to check the proposed config. When the canary is blocked before DLP, init names the scanner that blocked it. (#1758) -
pipelock canaryprints a JSON snippet that loads as config, and returns an error when either output format can't be written.pipelock posture verifyshows elapsed age in whole days andunknownwhen the generation time is missing; expiry checks are unchanged. (#1761) -
The reverse proxy now hard-blocks a critical credential in the URL path or query in enforce mode, even when
request_body_scanning.actioniswarn. Before this fix, the shipped presets forwarded such a request upstream while/fetchand the forward proxy refuse the same key. A body block on the reverse proxy now also counts toward the session's adaptive score, as URL and header blocks already did. (#1748) -
Reverse-proxy response blocks now carry the
X-Pipelock-Block-Reasonheader set, as the block-reason documentation says. Injection, compressed-response, oversize, scan-failure and media-policy blocks used to return the reason only in the JSON body. (#1748) -
The adaptive escalation table is corrected: the levels are reached at 1, 2 and 4 times
escalation_threshold, not 1, 2 and 3. The code always doubled the threshold after each escalation; the documentation was wrong. (#1748) -
A kill switch activation now cancels held MCP calls within about a second, for every source including
sentinel_file. It used to wait for the next request or for the resolver to finish. (#1748) -
A tool call or A2A request the kill switch refuses on MCP stdio, the stdio-to-HTTP bridge, the WebSocket proxy and the HTTP listener now leaves a signed
kill_switchblock receipt. A refused JSON-RPC batch is receipted for each tool call and A2A member, up to 64 per batch (a warning says how many were not), and is answered with one-32004error per member that has an id; the HTTP listener used to answer a refused batch with202and no receipt, and the other transports dropped it without a response. (#1748) -
The deferred-action API reports the decision actually applied. An approve that arrives after the kill switch activated, or that a release check refuses, now returns
final_decision: "block"instead ofallow. (#1748) -
The deferred-action API also reports
final_decision: "block", and the journal records a block, when a required receipt for the release cannot be written. The call is not sent, but the approve response and the journal saidallow. The allow receipt is now written after the journal accepts the allow, so a journal that cannot be written leaves a block receipt alone. With one receipt format the chain never holds an allow for a call that was not sent; with both formats configured, a failure in one leaves the other format's allow, followed by the block. (#1748) -
The reverse proxy's policy-replay record now matches the verdict it enforced. A critical credential in the URL or body that was hard-blocked under a
warnaction was recorded as a warning. (#1748) -
The reverse proxy, the forward proxy and TLS interception no longer relay an upstream
X-Pipelock-*header to the client. A 103 Early Hints response, a final response header, or a declared or undeclared trailer could carry a forgedX-Pipelock-Block-ReasonorX-Pipelock-Hint. Reverse-proxy block responses also no longer announce upstream trailers, and an upstream 103 no longer drops theX-Pipelock-Receipthandle from the reverse proxy's final response. (#1748) -
pipelock-verifier independent --require-full-coveragefails when the anchor covers fewer receipts than the chain holds. The default is unchanged: receipts after the anchor are chain-verified and reported throughcovered_receipts,chain_lengthandtail_chain_verified, and a broken tail exits non-zero. (#1748) -
The
upstream_contractrecipe now says how a contract rule names the bridge's upstream and what changes the release check's outcome. The Learn and Lock guide says a promoted manifest applies live, without a restart. The kill switch API andpipelock mcp proxydocumentation says which routes exist there. (#1748, #1759) -
A site whose first address is unreachable now connects through the next validated address instead of failing with 502. The proxy's and the SIEM forwarder's SSRF-safe dialers validated every resolved address but connected only to the first; they now try the validated addresses in resolver order and still refuse the host before any connect if any address is blocked. (#1748)
-
A clean reverse-proxy response that takes longer than 30 seconds to fetch and scan is delivered instead of ending in an empty reply. The listener's fixed 30 second write timeout cut off clean 48 MiB and 64 MiB responses from a
size_exempt_domainshost, which scan at roughly 0.7 to 1.3 seconds per MiB. The listener now bounds a client that stops reading, and caps how long one response may spend being written (10 minutes from its first write, with the 30 second stall window never extending past it) and how many connections it holds, instead of bounding scan time. A silent upstream is bounded by a 120 second response-header timeout. The response size ceiling and what is scanned are unchanged, and a response oversize_exempt_scan_max_bytesis still refused with a 403. (#1748) -
pipelock-verifier independentaccepts an anchor bundle after the receipt chain has grown. It compared the bundle's checkpoint with the whole chain, so every bundle stopped verifying as soon as one more receipt was written. It now recomputes the checkpoint over the receipts the bundle covers, fails a chain shorter than the bundle claims, and verifies the receipts after the covered prefix as a chain: a broken tail exits non-zero and is not reported as valid. The verdict addscovered_receipts,chain_length, andtail_chain_verified, and the OK line names the coverage. (#1748) -
Claude Code's
ReadandGreptools now go through the same credential-path policy as other file reads. They used to fall through to the generic content-only scanner, so reading an SSH private key,~/.aws/credentialsor/etc/shadowwithReadwas allowed even though the same path was already blocked forBashand MCP tools;Grepgets the same check when its path names a credential file, and aGrepover~/.ssh,~/.awsor a directory containing them, such as your home directory, is refused, as is one over a directory that contains/etc/shadow(/etcor/). A Grep that names no path is judged from its working directory. Both tools keep the content scan they had before, andNotebookEditwith a notebook path goes through the same write checks asWriteandEdit. Reading an SSH public key (id_*.pub) stays allowed, matching the existing Credential File Access exception. (#1739, #1743) -
A hard link to a file inside a protected directory is now treated as that file. Hard links to individual protected files such as
~/.aws/credentialswere already matched; a link to a file under~/.ssh,~/.config/systemd/user,~/Library/LaunchAgents, the cron, init and systemd directories,/var/logor Pipelock's state directory was not. Claude Code'sReadand single-fileGrep, the Cursor read hook and local MCP file tools now match it, including a link to a file in a subdirectory, such as a unit drop-in, and under both$HOMEand the home the account database records. The check runs only for a regular file that has another link. It walks each protected directory without following symlinks, skips anything on a different filesystem (a hard link cannot cross one), and treats a directory it cannot read in full on the same filesystem, or a tree of more than 8192 entries, as holding the file, whoever owns it. A multi-link file on the same filesystem as an unreadable protected directory is therefore refused even when it is unrelated. Each protected directory is walked once per tool call, however many path values the call carries. (#1748) -
Relative paths in the Claude Code and Cursor hooks are resolved against the session's working directory, the
cwdin the hook payload, not the directory the hook process was started in. A relative path through a symlink was checked against the wrong directory when the two differed. (#1748) -
The Claude Code
Grepcredential-directory check no longer depends on$HOMEalone. It also resolves the home directory recorded for the user the process runs as, checks both when they differ, ignores a$HOMEthat is not an absolute path, and still refuses when neither can be resolved. (#1748) -
The SSRF-safe dial fallback is bounded. The proxy and the SIEM forwarder try at most three validated addresses, in resolver order, and stop when the request's deadline passes, so a name with many unreachable records no longer holds a connection for one dial timeout per record. (#1748)
-
pipelock pi removereports what it actually did to Pi's settings file: restored a proxy setting Pi had before install, removed the proxy setting pipelock added where none existed, or deleted a settings file pipelock created. It used to always claim a restore, which was wrong in the other two cases. (#1739) -
pipelock hermes rollbackand containment rollback remove Pipelock's browser launch flag after you edit the agent-browserargs, keeping your edits, including arguments you removed. Hermes rollback names the flag it removed and the backup it saved. If you already removed the flag by hand, rollback clears its ownership record, so a later install no longer refuses with a stale-record error. (#1736) -
Release downloads through the proxy work again, and a block for an oversized response names only settings that can lift it. Outcome receipts record
exempt_over_cap_unscannedorincompleteinstead ofcompletewhen an exempt stream passes the scan ceiling or breaks mid-transfer. (#1730) -
A real GitHub release download also works on the fetch, forward-proxy, and TLS-intercepted paths that scan its redirect, not only the shape without an Azure SAS signature. GitHub's redirect carries a signed Azure SAS beside its download grant; the SAS is now admitted only next to that same verified grant for the exact storage host, with GitHub's full signed SAS parameter set present, each field in the format Azure documents and exactly one
sigparameter in the query, so a SAS without a valid co-located grant, on another host, or over plain HTTP still blocks. (#1739, #1743) -
Text DLP no longer reads joined English words as an AWS access key ID. Real
AKIAandASIAkeys split by spaces or invisible characters still block. (#1730) -
Each distinct dropped request-body DLP value is recorded once per request. A disabled or suppressed pattern used to add two or more to
pipelock_dlp_dropped_matches_totaland write repeateddlp_warnlines when redaction or the subdomain view re-reported it. Two different values, including two different seed phrases, are still counted separately; the same value repeated in one request is one record. (#1736) -
The 30-day limit on
response_scanning.core_observe_exceptionsexpiry is enforced at startup and bypipelock check, not only on hot reload. A fresh start used to accept an exception months ahead that a reload of the same file refused. (#1732) -
pipelock guardandpipelock sandboxwork on hosts whose CA bundle is a symlink outside/etc/ssl/and/etc/pki/, such as Arch Linux. Guard refused every launch as a narrowed policy, and a sandboxed process couldn't read its CA bundle. Both now grant the CA files as exact files; Guard still checks the resolved target against its compiled floor, and neither command grants the target's directory. (#1733) -
Request-body DLP inspects every
Content-Encodinga request declares. A credential header approved for one host is checked again when a redirect crosses to another host. A query with a malformed percent escape is entropy-checked as sent instead of skipping inspection, and query-entropy exclusions still apply. (#1728) -
Every receipt verifier checks the file the operating system actually opens. A path such as
link/../receipt.jsonlwas cleaned as text first and could verify different bytes from the resolved file; explicit files now follow the symlink before.., a symlinked evidence directory is refused, and the reference CLI,pipelock-verifier, and the TypeScript and Rust verifiers agree. Recorder timestamps are parsed from their original token, so Go 1.27 no longer accepts a timestamp Go 1.26 and the TypeScript verifier reject. (#1717) -
Default-on settings stay on when a config file omits them. Enabling Browser Shield from a config file now strips hidden prompt traps, extension probes and tracking pixels as documented, and
request_body_scanning.issuer_bound_session_cookiesis on for config files as well as the no-config path. An explicitfalseis still honored. (#1703) -
Tool policy protected-path rules cover equivalent operations. Move, rename, copy, delete, permission-change and link-creation tools now match rules that protect a destination path, patch targets come from patch headers, and backslash separators are recognized. (#1557)
-
Tool policy matches the file a local path argument resolves to. For a subprocess MCP server on the same host, or a Claude Code or Cursor hook, a symlink, hard link or relative name that reaches a protected file is matched as that file, and the shell startup-file and audit-log rules catch link-creating
ln,linkandcpcommands in every preset. Remote upstreams and shell command text are still matched as written. (#1718) -
A release stays a draft until its Helm chart attestation succeeds. The GitHub Release, the Homebrew formula and the floating major tag publish only after it, so a failed attestation stops the release before it's public. (#1716)
-
A containment install that fails partway restores what it changed. A step that edits the nft rules and their units, the integrity pin, the login profile script, the credential guard's service state or the evidence directory's access list and then fails reports the change so rollback restores it, and a rollback that can't finish is reported with the install error instead of only printed. (#1716)
-
The core DLP floor covers MCP input and the A2A-only forward branch, so a core credential in a tool call blocks even where the preset warns. (#1528)
-
MCP scanning covers content it used to skip: numeric leaves and tool definitions in responses, structured values under media-typed fields, the whole forwarded JSON-RPC envelope and session header,
structuredContentkeys, and SSE events with no data line. The listener enforces its state-token requirement on its own. (#1493, #1521, #1694) -
A2A header scanning checks every
A2A-Extensionsline. (#1686) -
Cross-request detection uses a keyed per-session bucket digest, requires classified identities, inspects a completing fragment before eviction, keeps exact contributors, clears the keys production writes on reset, and keys MCP state on the session key alone. Cross-request blocks emit receipts with a neutral client message. (#1489, #1517, #1526, #1547, #1563, #1579)
-
Partial (206) responses fail closed when Shield, media stripping or redaction would change their bytes, and decoding or truncating a partial body is refused. (#1653)
-
Empty and mistyped media responses pass or block with a named reason instead of a parse error. (#1643)
-
False positives in ordinary work: spaced assignments as URL credentials, prose read as AWS resource IDs, environment lookups in tool commands, base64 identifiers, bot checks, the jailbreak pattern inside encoded data, percent-encoded query exclusion keys, OAuth
redirect_uri, PKCE challenges and static asset hashes, including short hyphenated build hashes. (#1482, #1664, #1669, #1671, #1680, #1683, #1757) -
WebSocket relays end as soon as either side leaves instead of waiting out the idle timeout, and scoped DLP controls apply to frame scans. (#1604, #1674)
-
Go 1.27 compatibility fixes preserve signed evidence when it records invalid UTF-8. (#1673)
-
TLS interception finds its default CA in the Pipelock home (
--home,PIPELOCK_HOME, then~/.pipelock) and refuses to guess when two exist. (#1651) -
Containment hardening: the per-agent listener accepts only the relay account and root, the managed display requires X authorization, the exported CA stays current,
NODE_EXTRA_CA_CERTSuses the combined bundle, the config installs at0600so admin commands work, and YAML null or aliased containment settings count as absent. (#1624, #1635, #1636, #1640, #1679) -
A contained agent's listener refused for a missing license now says so. Without a license, named agent profiles are disabled at load, and a
containment.agent_listenerpointing at one was refused as undeclared. The config is still refused, but the error names the profile and says named profiles need a Pro license with the agents feature. (#1720) -
A provider key sent to its own issuer passes query entropy when the entire value is one built-in credential that DLP already allows for that destination. Extra bytes, other parameters and every other scanner are unaffected, and the semicolon-separated query path stays strict. (#1708)
-
An allowed image served under the wrong raster type is classified by its complete image header, handled like the same bytes correctly labeled, and forwarded with the proven type in
Content-Type, the fetch JSON response and MCPmediaType/contentType. (#1708) -
pipelock contain viewreaches the display viewer again for the configured operator, and a viewer socket permission error names both possible causes. (#1708) -
A long high-entropy environment value no longer stops Pipelock from starting. A known value over 4,096 bytes, such as an inline certificate or JSON key under
scan_env, is indexed at up to 4,081 evenly spaced positions instead of being refused, so a copied fragment is still caught once it covers one of them. A long URL-shaped value is sampled as one value in source order. (#1712) -
Mislabeled JPEG and PNG bodies are refused when their actual format is disallowed, including when metadata stripping is off on the proxy or MCP path. (#1711)
-
Issuer-returned query values use the URL entropy gate's exact-session allowance for same-origin JSON links and redirect locations. The two OAuth authorization-code redirects can carry their issued
stateandnonce, orcode,stateandiss, between the client and the authorization server when the session declared that callback; any other parameter keeps the ordinary gate, the relief is off whenrequest_body_scanning.content_entropy_actionisblock, and DLP and the other URL checks still run. (#1711) -
MCP subprocess handling: descendant cleanup is reported before startup, active approval resolvers survive child cleanup, binary locations report unknown instead of not-suspicious, and tool rules reload from one snapshot. (#1516, #1550, #1586, #1596, #1639)
-
Reverse-proxy request blocks score each distinct finding for adaptive enforcement. Every URL, header and body block was recorded under one shared scanner name with an empty reason, so a different finding against the same upstream looked like a retry of the first and the session score never climbed from reverse-proxy blocks alone. Identical retries still score once. The receipt and metric layer stays
dlp. (#1755) -
A crash that leaves a partly written last line in an evidence shard no longer wedges the next reload. Pipelock never appends to or rewrites the damaged shard. A reload that meets one starts a fresh run session and publishes the config only after the new receipt emitter opens, and a local anchor log with a torn final write continues in a numbered
.segment-file beside it. Malformed complete records, bad signatures and broken hash links still reject the reload.pipelock evidence doctorreports the shard as damaged,evidence_health.torn_tailsandtorn_tail_presentin/statsrecord it, andpipelock_evidence_torn_tails_totalandpipelock_evidence_torn_tail_presentexpose it for alerting until the process restarts. (#1750) -
Live receipt auto-anchoring no longer loads the whole chain into memory. Checkpoints are built by streaming the chain, with bounded temporary spill files that are cleaned up and swept when stale, and the full chain on disk is still verified every time. (#1754)
-
The reverse proxy gives each upstream response its own copy. Closing a blocked response's body early let net/http's background drain write upstream trailers into the same struct the proxy was rewriting, a data race. Trailers on clean responses are still relayed. (#1751)
-
Browser Shield strips a comment trap without eating the markup between separate comments. Matching now happens inside each parsed comment, so stylesheets and scripts between two comments stay intact, and XML processing instructions, SVG and MathML content and an unterminated instruction are handled. (#1753)
-
pipelock sandbox --strictlets native runtimes create threads. Strict seccomp now answersclone3withENOSYSinstead ofEPERMonlinux/amd64, so glibc retries with the argument-filteredclonecall.clone3still never runs and namespace-creatingclonestays denied. (#1749) -
Evidence auditor targets survive
initreruns. (#1490) -
The runtime fails closed on a reload that fails partway, leaving the behavioral baseline action unchanged, and the support bundle refuses an output path that already exists. (#1696)
-
Go, TypeScript and Rust verifiers check every present receipt chain and recorder boundary in directory, named-run and file modes. They agree on key rotation and unpinned signature checks, and a failed audit packet can't report trusted evidence. The anchor-bundle schema loads under strict validators. (#1611, #1656, #1658, #1697, #1713)
-
Rekor anchoring accepts sharded logs whose entry index differs from the tree index. (#1470, #1622)
-
License service: one active trial per email across writers, the trial slot table as the only eligibility authority, atomic webhook revocation, and removal of a stale founding deadline. (#1556, #1597, #1606, #1698)
-
The GitHub Action keeps repository-derived text out of workflow commands. Annotations and the job summary escape it, the audit report no longer prints file names into the job log, and config validation output runs with workflow commands stopped. (#1698, #1704)
-
Playground: the durable signing root stays off visitor VMs, delegated runs seal against their root, published replays stay verifiable, a broker without a usable root refuses to start, visitors get their own evidence unedited, and kits stay downloadable. Runs record the requested and provider-reported model, and the broker's admin listener, key cache, artifact reads and VM slots are hardened. (#1442, #1444, #1449, #1450, #1459, #1583, #1646, #1647, #1649, #1695)
-
The receipt example's tamper step edits a signed field, so it now proves tamper detection. (#1445)
-
The logo PNG renders transparent and scaled, with a generated raster ladder and
.ico. (#1519)
Removed
- The never-imported
internal/abomandinternal/manifestpackages. (#1518)
Documentation
- Onboarding checks state their scope. Init's local canary and
verify-install's temporary-proxy fixtures distinguish synthetic verification from real-client routing, with configuration provenance in human and JSON output. False-positive recovery starts with an explanation, the smallest applicable correction and a recheck. (#1758) - Install and release examples, receipt anchoring and SCITT scope, and the MCP upstream SSRF exception are corrected. (#1431, #1432, #1468, #1594)
- The internal release checklist moved out of the public repository. (#1608)
- Configuration, tool policy, receipt verification, flight recorder, integration and containment guides are corrected to match current behavior, including that a promoted Learn and Lock manifest applies live, and that the sandbox guide covers Ubuntu's AppArmor user-namespace restriction.
audit-packet --helpsays trust needs--keyor--expect-sha256. The kill switch docs count seven activation sources, including uncertain Conductor apply, which also ignores IP allowlist exemptions. Mediation envelope examples are checked against the production serializers, and an examples index lists each example's runtime requirements and verification command. (#1736, #1748, #1755, #1759, #1764, #1766) - The contain guide lists what the signed workspace change statement records, which is paths, counts and completeness and no per-file sizes, timestamps or digests. (#1729)
- A browser reproduction guide describes a synthetic diagnostic runner and says standalone
sandbox --strictdoesn't support Chromium's own sandbox. The forward-proxy redirect, mediation envelope and transport guides describe the client-followed redirect behavior. (#1749) - The WebSocket proxy example's verify script stops its echo helper when it exits. (#1735)
Dependencies
- The standalone receipt verifier packages move to 0.4.1 (
@pipelock/verifier-tson npm,pipelock-verifier-rson crates.io) so they are built from the same verifier source as this release. (#1771) - Weekly dependency updates,
fast-uri3.1.8 in the TypeScript verifier, and@unicode/unicode-15.0.02.x. (#1416, #1441, #1469, #1492, #1742) - The init and license-service images use Alpine 3.24.2 (OpenSSL 3.5.8), and the source Dockerfile builds with Go 1.27.1. (#1716)
Testing and CI
- Fail-closed and error-path coverage across signed evidence, receipts, anchoring, containment, deferred resolution, sessions, guard setup, sidecar topology, config parsing, TLS files and rule updates. (#1497, #1499, #1500, #1501, #1502, #1503, #1504, #1505, #1506, #1508, #1509, #1510, #1511, #1512, #1513, #1514, #1515, #1549, #1552, #1574, #1585, #1684)
- Flaky and timing-dependent tests fixed, including scanner drain, debounce, deadlines, subprocess timeouts, calendar-dated fixtures, sandbox bridge keep-open and contained display tests. (#1438, #1439, #1457, #1462, #1463, #1464, #1485, #1488, #1491, #1544, #1587, #1605, #1645, #1705, #1715)
- New benchmarks and transport proofs: response scanning at real page sizes, and identity-encoded scanned responses for browsers. (#1612, #1660)
- The Gauntlet benchmark gate owns Pipelock's acceptance policy and tracks the current bench revision and record schema, and now pins Agent Egress Bench v1.1.0 (corpus v2.14.0) with a matching baseline and acceptance record. (#1433, #1434, #1436, #1440, #1443)
- CI runs the Go floor on pull requests, proves both supported Go versions on main, proves compatibility gates fail closed, keeps full timeout diagnostics, and warms the MCP end-to-end package before timing. (#1448, #1480, #1592, #1614)
- The proxy and scanner race-test shards are each split in two by test name, bringing both back well under the 20-minute shard timeout. The last half of each skips exactly what the first runs, so a new test still runs once, and CI fails if a shard command stops passing its selector. (#1721)
- The Python, Rust and TypeScript verifier corpus tests run every AARP corpus category and fail when one isn't wired in, and the benchmark gate fails a full run when a baseline benchmark disappears or rises from a zero baseline. (#1722)
- The pull-request AI review shows unfinished reviews on the current head, stays informational, retries rate-limited chunks, and uses current model tiers and budgets. (#1472, #1525, #1533, #1542, #1543, #1559, #1648)
- An internal review record was removed from the source tree. (#1719)
📚 Docs: https://pipelab.org • 💬 Community: https://discord.gg/badNfhGKTc
Pipelock is an open-source agent firewall. Come poke holes in it.