Go telemetry agent for the [Guard ecosystem](https://github.com/Guard-Core/guard-core). Buffers security events, metrics, and agent status locally and ships them to the Guard Core App ingestion API with at-least-once delivery: nothing acknowledged is lost, nothing unacknowledged is forgotten.
Website · Docs · Playground · Dashboard · Discord
Guard Core is the Python engine. Framework adapters are thin wrappers that translate native request/response types into Guard Core's protocols. The telemetry agents ship security events and metrics to the monitoring backend. Parallel engine implementations exist for Go, PHP, TypeScript (on npm), and Rust (on crates.io) - all ports of the same reference semantics, conformance-tested against the shared adversarial corpus.
| Package | Role | PyPI |
|---|---|---|
| guard-core | Framework-agnostic security engine | |
| guard-agent | Telemetry agent | |
| fastapi-guard | FastAPI / Starlette adapter | |
| flaskapi-guard | Flask adapter | |
| djapi-guard | Django adapter | |
| tornadoapi-guard | Tornado adapter |
Go modules published via GitHub releases. Production-ready.
| Package | Role | Release |
|---|---|---|
| guard-core-go | Go engine | |
| nethttp-guard | net/http adapter | |
| gin-guard | Gin adapter | |
| echo-guard | Echo (v4) adapter | |
| fiber-guard | Fiber (v3) adapter | |
| guard-agent-go | Telemetry agent |
Published on Packagist under the rennf93 vendor. Production-ready.
| Package | Role | Packagist |
|---|---|---|
| guard-core-php | PHP engine | |
| laravel-guard | Laravel adapter | |
| symfony-guard | Symfony adapter | |
| psr15-guard | PSR-15 adapter | |
| slim-guard | Slim 4 adapter | |
| guard-agent-php | Telemetry agent |
Published under the @guardcore npm scope; source in the guard-core-ts monorepo. Production-ready.
| Package | Role | npm |
|---|---|---|
| @guardcore/core | Core engine | |
| @guardcore/express | Express adapter | |
| @guardcore/nestjs | NestJS adapter | |
| @guardcore/fastify | Fastify adapter | |
| @guardcore/hono | Hono (edge) adapter | |
| guardagent | Telemetry agent |
Published on crates.io. Production-ready.
| Package | Role | crates.io |
|---|---|---|
| guard-core-engine | Core engine crate | |
| guard-core-rs | Facade crate (consumer entry point) | |
| actix-guard-rs | Actix Web adapter | |
| axum-guard-rs | Axum adapter | |
| tower-guard-rs | Tower adapter | |
| rocket-guard-rs | Rocket adapter | |
| guard-agent-rs | Telemetry agent |
| Package | Role | PyPI |
|---|---|---|
| guard-core-mcp | MCP server: config validation, docs search, detection sandbox |
📚 Documentation - full technical documentation for this package.
🛡️ Guard Core - the engine's reference documentation.
🤖 Monitoring Agent Integration - monitor your Guard instance with a monitoring agent.
Released. Version v3.2.2 is tagged and published to the Go module proxy; releases are cut as v* git tags.
- Per-kind queues (events, metrics) with size and time flush triggers and a high-watermark early flush.
- Overflow policies:
drop(default),block,raise. - At-least-once handshake: drain, send, then confirm or requeue in the original order.
- gzip request bodies and an HMAC-SHA256
v1=signature that covers the uncompressed body, which is what the server verifies after decompression. - Retry with exponential backoff,
Retry-Afterhonoring (capped at 300s), 413 recursive split-or-drop, 400/404/422 permanent rejection, 200-partial requeue, per-kind failure-streak backoff, and a 5-failure / 60s circuit breaker. - Optional Redis persistence: write-on-enqueue with a TTL, delete-on-confirm, reload on
Start, fail-open on any Redis error. - Stable install identity persisted to
~/.guard-agent/install-id. - Failure isolation: no exported method panics out or blocks the host beyond the configured overflow policy.
go get github.com/rennf93/guard-agent-go/v3@v3.2.2Package name is guardagent; the module is github.com/rennf93/guard-agent-go/v3.
package main
import (
"context"
"log"
"github.com/rennf93/guard-agent-go/v3"
)
func main() {
cfg := guardagent.DefaultConfig()
cfg.APIKey = "your-ingest-api-key"
cfg.ProjectID = "your-project-id"
// cfg.Endpoint = "https://your-guard-core-app.example.com"
agent, err := guardagent.New(cfg)
if err != nil {
log.Fatal(err)
}
if err := agent.Start(context.Background()); err != nil {
log.Fatal(err)
}
defer func() { _ = agent.Stop(context.Background()) }()
ctx := context.Background()
if err := agent.SendEvent(ctx, guardagent.SecurityEvent{
EventType: "penetration_attempt",
IPAddress: "203.0.113.7",
Method: "GET",
Endpoint: "/admin",
Reason: "suspicious pattern",
}); err != nil {
log.Printf("telemetry: %v", err)
}
_ = agent.SendMetric(ctx, guardagent.SecurityMetric{
MetricType: guardagent.MetricRequestCount,
Value: 1,
})
}Redis durability is one field away and never makes the agent depend on Redis being up:
cfg.Redis = &guardagent.RedisConfig{URL: "redis://localhost:6379"}
cfg.SigningSecret = os.Getenv("INGEST_PAYLOAD_SIGNING_SECRET")| Situation | Behavior |
|---|---|
Buffer full, drop (default) |
Evict the oldest item of that kind, confirm its persisted record, log every 100th drop |
Buffer full, block |
Wait for a flush to free space (returns ctx.Err() if the context ends first) |
Buffer full, raise |
SendEvent/SendMetric return *BufferFullError (errors.Is(err, guardagent.ErrBufferFull)) |
| Flush trigger | Combined occupancy at or above BufferSize * HighWatermarkRatio, or FlushInterval elapsed |
| 200 | Confirm: delete persisted records, reset the failure streak |
200 with success:false or errors[] |
Requeue the whole batch in original order (at-least-once: duplicates possible, losses are not) |
| 429 | Honor Retry-After (60s default, 300s cap) and retry |
| 413 | Halve the batch and send both halves; a singleton that still 413s is dropped durably |
| 400 / 404 / 422 | Permanent: drop durably, never requeue |
| 401 / 403 / 5xx / network | Retry with BackoffFactor * 2^attempt (60s cap) |
| Repeated per-kind failure | Gate that kind for min(FlushInterval * 2^(streak-1), 300s) |
| 5 consecutive transport failures | Circuit breaker opens for 60s, then admits one probe (half-open); permanent rejections and 413s are exempt |
Status() |
healthy, degraded (breaker open, 90% occupancy, or >10% lifetime failure rate), or failed after Stop |
| Redis unavailable | Log, count, keep going; writes pause for 30s after 3 consecutive failures; records expire by TTL |
Stop |
Cancel the loops, then one final flush that bypasses the backoff gates |
The ingestion API verifies X-Payload-Signature after its gzip middleware decompresses the request (telemetry_router.py, payload_signature.py), so the signature must be an HMAC-SHA256 over the uncompressed JSON. This agent signs before compression and sends v1=<hex> accordingly.
The Python and TypeScript agents sign the post-compression wire bytes instead, so their signatures stop verifying as soon as gzip kicks in. That mismatch is a known defect on their side; this agent intentionally does not reproduce it, and the test suite asserts the server-side semantic with a mock that decompresses first and verifies second.
GetDynamicRules(ctx) fetches the SaaS rule document from GET /api/v1/rules (the full Python-agent DynamicRules surface) and caches it for the document's ttl; a failed poll serves the last known rules. Start runs a background polling loop on DynamicRuleInterval, and Stats().RulesFetched counts refreshes. TruncatePayload, HashIP, and KnownEventTypes round out the host-adapter helper surface.
Start from guardagent.DefaultConfig() and override. RetryAttempts and CompressionThreshold are zero-honest (0 means zero retries, and 0 compresses every body); the feature booleans are false in a hand-built Config{}.
| Field | Default | Notes |
|---|---|---|
APIKey |
required | At least 10 characters |
Endpoint |
https://api.guard-core.com |
Trailing / and /api/v1 are stripped |
ProjectID |
empty | Sent as X-Project-Id |
BufferSize |
100 | Per-kind capacity |
FlushInterval |
30s | Flush cadence and backoff base |
StatusInterval |
300s | Minimum 60s |
DynamicRuleInterval |
300s | Dynamic rules polling cadence, minimum 60s |
HighWatermarkRatio |
0.8 | Early flush threshold |
MaxConcurrentFlushes |
1 | Wake-driven flushers |
Overflow |
drop |
drop, block, raise |
RetryAttempts |
3 via DefaultConfig() |
0 disables retries |
Timeout |
30s | Per request |
BackoffFactor |
1.0 | Seconds, base of 2^attempt |
CompressionEnabled / CompressionThreshold |
true / 1024 | gzip at or above the threshold |
SigningSecret |
empty | Enables X-Payload-Signature |
SensitiveHeaders |
defaults | Header names redacted from event metadata and metric tags (case-insensitive); nil = defaults, non-nil replaces |
MaxPayloadSize |
1024 | Payload size (bytes) to truncate at with TruncatePayload before embedding in event metadata |
OnError |
nil | Best-effort failure callback (stage, err, context); stages: transport_send, encryption, flush_events, flush_metrics |
InstallID / InstallIDPath |
auto / ~/.guard-agent/install-id |
Override either |
Redis |
nil | URL, Prefix (guard:agent), TTL (1h) |
GuardVersion / GuardCoreVersion |
empty | Reported to the API |
go build ./...
gofmt -l .
go vet ./...
go test ./...
REDIS_HOST=127.0.0.1 go test -tags integration ./...
go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...The integration build skips itself when REDIS_HOST is unset. On hosts without a Go toolchain, run the same commands in golang:1.25-alpine with a redis:7-alpine container reachable as redis on a shared Docker network.
- guard-core: the Python engine that anchors the ecosystem.
- guard-agent / guard-agent-rs: Python and Rust sibling agents.
- guard-core-app: hosts the ingestion API.
- gin-guard / nethttp-guard: Go adapters that emit the events.
MIT