Repository navigation
Releases: Guard-Core/guard-agent
Release list
v3.2.4
v3.2.4 (2026-10-15)
Redis persistence off the request path
Persisting events and metrics to Redis no longer happens under the buffer lock. The in-memory append stays on the request path and the Redis write runs as a tracked background task that stop_auto_flush drains, so a slow or failing Redis no longer delays requests. A Redis failure can no longer re-enter persistence, which removes a deadlock that blocked every later send_event once Redis started failing. In-flight writes are capped at buffer_size: past the cap an item stays in memory only, counts as a persist failure, and one warning is logged per backlog episode. Items flushed or evicted before their write starts are never written, and a failed write no longer leaves tracking state behind. Unpadded base64url project keys are accepted, and the startup encryption error now includes the underlying cause. Lockstep release with the agent family at 3.2.4.
v3.2.3
v3.2.3 (2026-10-12)
Version reporting fix
The 3.2.2 wheel shipped guard_agent/_version.py at 3.2.1, so 3.2.2 installs reported 3.2.1 to the Guard SaaS. Every version location now reads 3.2.3. Lockstep release with the agent family at 3.2.3.
v3.2.2
v3.2.1
v3.2.1 (2026-10-06)
The lockstep release: version alignment with the engines at 4.3.1 (v3.2.1)
No agent behavior changes. Shipped so the agents family moves in lockstep with the engines at 4.3.1 while guard-agent-ts carries the js/polynomial-redos fix (linear stripTrailingSlashes replacing anchored quantifier regexes in config.ts/transport.ts, CodeQL HIGH).
v3.2.0
v3.2.0 (2026-10-01)
Buildable dev resolution, Slack-free CI, and an enforced 100% coverage gate (v3.2.0)
Fixed
guard-agent[dev]installs resolve again (#65). The repo commits no uv.lock, so everyuv sync --extra devre-resolves fresh; with fastapi in the dev extra, that fresh resolution backtracked semgrep all the way to 0.86.3, whose sdistsetup.pyaborts without a system semgrep-core binary. That brokeguard-agent[dev]installs in guard-agent's own CI and in the guard-core ecosystem-gate consumer jobs, and shipped in the 3.1.0 metadata. semgrep is now floored at>=1.100,<2, so resolution lands on a wheel release (1.178.0) and the dev extra installs cleanly again.
Changed
- CI no longer posts Slack notifications (#64). The Slack notification steps were removed from ci.yml, code-ql.yml, docs.yml, release-shim.yml, release.yml and scheduled-lint.yml.
- The 100% coverage gate is now enforced in CI (#64). The test job runs pytest with
--cov-fail-under=100, so a coverage regression fails the build instead of only being reported; this matches the project's 100%-coverage target. - github/codeql-action bumped from 4.38.1 to 4.38.2 (#63).
v3.1.0
v3.1.0 (2026-09-27)
The parity release: agent feature surface locked across all four language ports (v3.1.0)
About this release
- This is the parity release for the Guard agent family. The 3.1.0 wave (Python, TypeScript, Go, PHP, Rust) ships the same feature surface in every port. Note: the earlier 4.1.0 family tags were a version-accuracy error and were yanked/unpublished; 3.1.0 is the correct version for this train.
- The conformance corpus (219 cases, spec 4.1.0) validates the shared surface across the ports.
Added (3.0.2 -> 3.1.0 feature list)
- AES-256-GCM encrypted ingest. Event batches can be encrypted end to end with an AES-256-GCM key; the server decrypts after transport-level TLS, so batch contents are opaque to intermediaries even on the wire.
- Sensitive-header redaction. Authorization, Cookie, Set-Cookie, and other configured sensitive headers are redacted at ingest and again at egress, so secrets never leave the process in plaintext form.
- Dynamic rules. The agent polls the guard-core server for dynamic rate-limit and ban rules on a configurable interval and enforces them locally, emitting
dynamic_rule_applied,dynamic_rule_updated, anddynamic_rule_violationevents. - Local rate limiters. Rule-driven local rate limiting with auto-ban support, usable without a Redis round trip on every event.
- Helpers. Shared helpers for event construction, batching, and redaction are exported for adapter authors building custom ingest paths.
v3.0.2
Fixed
- The payload signature was computed over the compressed wire body, so every compressed request failed server-side verification. When gzip compression kicked in (payload at or above
compression_thresholdwithcompression_enabled),X-Payload-Signaturewas an HMAC over the gzipped bytes, while the server verifies after decompressing; compressed batches were therefore rejected as signature-invalid. The signature now always covers the uncompressed body, matching the server contract and all four agent ports (Go, PHP, Rust, TypeScript). Regression tests verify the signature equals the HMAC over the decompressed wire body on both the encrypted and unencrypted POST paths. - The package version and the reported agent version could drift apart. The 3.0.1 release updated
pyproject.tomlbut leftguard_agent/_version.py(the single source of truth used for the User-Agent andagent_versionon every batch) at 3.0.0. Both are now 3.0.2, and a new consistency test fails if the two files ever diverge again.
Changed
- The deprecated
fastapi-guard-agentalias now resolves to guard-agent 3.x. Its dependency pin was stillguard-agent>=2.0.0,<3.0.0from the 2.0.0 rename, so installs through the alias kept resolving to 2.x after 3.0.0 shipped. The pin is now>=3.0.0,<4.0.0and the shim version is 1.3.0; the shim itself publishes via the manualrelease-shim.ymlworkflow after the main release.
v3.0.1
v3.0.0
Recursive header and metadata redaction, an atomic block-policy buffer, and a maintainability split (v3.0.0)
Breaking Changes
BufferProtocol.requeue_events_in_memoryandrequeue_metrics_in_memoryare now async. A failed send's requeue now shares the same lock as every other buffer mutation, so both methods on the protocol (and onEventBuffer) must be awaited. A customBufferProtocolimplementation needs to change its signatures toasync def;GuardAgentHandler's own buffer andFlushMixinalready call them withawait.sanitize_headersnow redacts what it cannot safely classify instead of letting it through. A JSON-looking string over 8192 characters, a value of a type it does not recognize (a generator, an arbitrary object), and a bytes header key that fails to decode as UTF-8 previously passed through unchanged or matched nothing; all three now become[REDACTED]. A payload that relied on that pass-through will see[REDACTED]there instead.
Added
sanitize_headersrecurses into every container and into JSON-looking strings, including a double-encoded JSON string. Dicts, lists, tuples, sets, frozensets and JSON string bodies are all walked (up to a bounded depth), not just the top-level mapping; a JSON string whose decoded value is itself a JSON string is unwrapped and sanitized too, then re-encoded the same way.datetime,date,time,Decimal,UUIDandEnumvalues pass through unchanged. They are legitimate scalar metadata, not unrecognized objects to redact.- A dataclass instance or a pydantic model passed as metadata is walked field by field, like a mapping, via
dataclasses.asdict/model_dump, instead of being collapsed to[REDACTED]wholesale. - The default
sensitive_headersset now matches guard-core's:proxy-authorizationjoinsauthorization,cookieandx-api-key. KNOWN_EVENT_TYPESgainedip_ban_failed,rate_limit_script_reloaded,pattern_anomaly_timeout,pattern_anomaly_statistical_anomalyandroute_unresolved, aligning with guard-core 4.0.0's event catalogue. A version-gated test skips until the installedguard_corereaches 4.0.0 and fails on any other drift once it does.tests/test_maintainability_rank.pyenforces radon MI rank A on every module inguard_agent.on_erroralso fires at the flush boundary (stage='flush_events'/'flush_metrics'), once per failed batch, with the exception and{"batch_size": N}, through the samefire_error_hookhelperHTTPTransportuses. Previously it only fired inside the transport's own retry paths.
Fixed
- A bytes header key no longer bypasses the sensitive-header match.
b"authorization"is decoded as UTF-8 before the case-insensitive comparison instead of being stringified to the literal"b'authorization'", which never matched anything. - The event and metric buffers each serialize their overflow-check-and-append, flush, requeue and clear through one
asyncio.Lock. Two concurrentadd_event(oradd_metric) calls at capacity could previously both evict the same slot, appending past it and orphaning the other call's Redis record; the check and the append are now one atomic step. - The block overflow policy waits on an
asyncio.Conditionbound to that same lock and re-checks at least once every 0.5 seconds. A requeue after a failed send always keeps its slot (durability over a new writer), and a blocked writer no longer depends solely on an explicit signal that a send-failure backoff (up to 300 seconds) can delay past. clear_buffernow clears the Redis-key maps along with the buffers. Previously only the deques were cleared, so a later event or metric object could inherit a stale or foreign key once CPython reused itsid().- A Redis record is deleted on a requeue eviction and on an overflow drop without racing the buffer it was tracked against.
- The Makefile's
semgreptarget scansguard_agentwith--no-git-ignore, so a new, not-yet-tracked file is scanned deterministically instead of depending on git state. - A
BaseExceptionfrom the transport (anasyncio.CancelledError, not anExceptionsubclass) no longer skips the batch's requeue and failure bookkeeping._flush_events/_flush_metricscatchBaseException, requeue the popped batch and advance the failure streak and backoff before re-raising, so a cancellation still propagates but never silently drops the batch. - Loading persisted events/metrics from Redis at startup no longer races live
add_event/add_metrictraffic. Each load now runs under its buffer's own condition lock, and forgets the oldest tracked key before its own append would otherwise evict an item past capacity, so a startup load can never orphan a Redis record or corrupt the key map racing a concurrent write.
Internal (v3.0.0)
buffer.py,client.pyandtransport.pywere split into focused mixins (_buffer_lifecycle,_buffer_overflow,_buffer_queue,_buffer_redis;_client_flush,_client_ingest,_client_loops,_client_status;_transport_dispatch,_transport_lifecycle,_transport_send), each independently testable and each rank A.anyio<4.15andsemgrepwere added to thedevextra.
What's Changed
- release(v3.0.0): redaction at ingest and egress, lossless atomic buffer, rank-A split, event-type parity by @rennf93 in #52
Full Changelog: 2.10.0...3.0.0
v2.10.0
Agent logs that identify themselves (v2.10.0)
Added
- guard-agent log lines now carry an origin prefix.
setup_agent_loggingattaches guard-core's standard formatter ([guard_agent.client] 2026-09-01 12:00:00 - WARNING - ...) to theguard_agentlogger tree, so agent records such asEvents flush recovered after N consecutive partial failure(s)are identifiable in hosted log viewers instead of arriving bare. It runs automatically when aGuardAgentHandlerorSyncGuardAgentHandleris constructed, and the function is exported for explicit host use:setup_agent_logging(log_file=..., log_format="json"). - JSON output and an optional file sink.
log_format="json"emits{"timestamp", "level", "logger", "message"};log_file=...adds aFileHandleralongside the console handler. A console handler is only attached when the root logger has no handlers, matching guard-core's yield-to-host contract, so records still reach host-configured logging through propagation.
Changed
- The automatic setup is non-destructive: hosts keep their logging configuration. Constructing the handler attaches at most one console handler, never changes a level the host set, and never removes a host-configured file handler or formatter, no matter how many times the handler is constructed. Hosts reconfigure by calling
setup_agent_logging(...)explicitly, which re-applies intent (agent handlers are cleared first). - The old async
setup_agent_loggingstub inguard_agent.utilsis gone. It was never called and had zero consumers across the ecosystem, and its export now points at the real implementation inguard_agent.logging_utils. The signature changed fromasync (log_level: str)to(log_file: str | None, log_format: str, *, reconfigure: bool).
Internal (v2.10.0)
- pytest now runs with
filterwarnings = ["error"], so any warning, including one raised at import/collection time, fails the suite instead of passing silently. - That escalation surfaced a real resource leak:
tests/test_adapter_fastapi.py'sSecurityConfigdidn't disable Redis, leaving an unclosed async Redis socket to raise aResourceWarningat a later, unrelated test's garbage collection. Addedenable_redis=False, the same fix already applied totest_performance.pyin v2.3.0 to eliminate ResourceWarning pollution.
What's Changed
- chore(conventions): scaffold .roboco/conventions.yml by @roboco-app[bot] in #48
- Bump actions/github-script from 7 to 9 by @dependabot[bot] in #46
- Bump github/codeql-action from 4.37.8 to 4.37.9 by @dependabot[bot] in #47
- feat(logging): identifiable agent logs with non-destructive setup (v2.10.0) by @rennf93 in #49
New Contributors
- @roboco-app[bot] made their first contribution in #48
Full Changelog: 2.9.1...2.10.0