The Autonomous Stack
Webhook Guard Agent icon

Webhook Guard Agent · AI Security · v1.0

Verify webhook signatures and fail closed

Fail-closed signed webhook admission for autonomous agent pipelines.

$3.00one-timePay with crypto

Buy on Gumroad → https://6355137346884.gumroad.com/l/tas-agent-webhook-guard-v1

webhook-security hmac replay-protection offline

agent ⟶ theautonomousstack.xyz

01GET /api/v1/pack/agent-webhook-guard-v1

402 Payment RequiredPAYMENT-REQUIRED

02sign TransferWithAuthorization3.00 USDC · Base (eip155:8453)

03GET + PAYMENT-SIGNATURE

200 OKapplication/zip · 18 files

pay per pack over x402no account

What's inside

  • CHANGELOG.md
  • CHECKLIST.md
  • CUSTOMIZE.md
  • OPERATING_COST_ESTIMATE.md
  • PROMPTS.md
  • README.md
  • SOUL.md
  • SPEC.md
  • agent-webhook-guard-v1.json
  • agent-webhook-guard-v1.service
  • agent.py
  • deploy.sh
  • install.sh
  • llm_adapter.py
  • pack_memory.py
  • requirements.txt
  • smoke_test.sh
  • test_agent.py

Admit signed webhook events to an agent pipeline without trusting the payload.

Webhook Guard is for operators who let billing, CRM, deployment, support, or automation systems trigger autonomous workers. It verifies HMAC-SHA256 signatures, rejects stale events, records accepted event IDs in SQLite, and quarantines malformed, forged, expired, or replayed envelopes before downstream automation sees them.

Honest scope

The admission decision is deterministic and works offline. The optional local LLM CLI sees only verified metadata (source, event_id, event_type, and content_type) and may suggest a queue. It never sees the webhook body and cannot change an accept/reject verdict.

Capability Deterministic core Optional LLM
HMAC verification Yes No
Timestamp window Yes No
Replay protection Yes No
Quarantine and audit report Yes No
Conservative queue selection Yes No
Metadata-only routing suggestion Fallback Yes
HTTP server or reverse proxy No No
Provider-specific signature formats Customize No

This pack consumes local JSON envelope files. Your existing HTTP gateway must write those files after preserving the exact request body. See CUSTOMIZE.md.

Who it is for

  • LLM and automation operators accepting external event triggers.
  • Small teams needing a fail-closed ingress gate without a hosted dependency.
  • Integrators testing webhook authenticity and replay behavior offline.
  • Security reviewers who need a compact, inspectable admission ledger.

Quick start

Install on Debian or Ubuntu:

sudo bash install.sh

Or run in place without root:

export WEBHOOK_GUARD_SECRET='replace-with-at-least-16-random-bytes'
python3 agent.py --help
python3 -m unittest -q test_agent

Create and sign a local example:

NOW="$(date +%s)"
export NOW
python3 -c 'import json,os; print(json.dumps({
  "source":"billing",
  "event_id":"evt-demo-001",
  "event_type":"payment.succeeded",
  "timestamp":int(os.environ["NOW"]),
  "body":"{\"amount\":1200,\"currency\":\"USD\"}"
}))' > /tmp/webhook.json
SIG="$(python3 agent.py sign --input /tmp/webhook.json)"
export SIG
python3 -c 'import json,os; p="/tmp/webhook.json"; d=json.load(open(p)); d["signature"]=os.environ["SIG"]; open(p,"w").write(json.dumps(d))'
python3 agent.py verify --input /tmp/webhook.json --no-record

For a durable replay ledger, omit --no-record. A second verification of the same (source, event_id) returns exit code 2 with verdict replay.

Event envelope

{
  "source": "billing",
  "event_id": "evt-123",
  "event_type": "payment.succeeded",
  "content_type": "application/json",
  "timestamp": 1784912400,
  "body": "{\"amount\":1200,\"currency\":\"USD\"}",
  "signature": "sha256=0123456789abcdef..."
}

The signed bytes are:

ASCII(timestamp) + "." + exact UTF-8 body bytes

Use body_base64 instead of body for binary data. Exactly one body field is required.

Architecture

provider webhook
       |
       v
existing HTTP gateway
       |
       | exact body + signature + timestamp + event ID
       v
local inbox/*.json
       |
       v
+-----------------------+
| Webhook Guard         |
| 1. bound input size   |
| 2. verify HMAC        |
| 3. check clock window |
| 4. reserve event ID   |
+----------+------------+
           |
      +----+----+
      |         |
 accepted/   quarantine/
      |         |
 safe route   audit only
 suggestion
      |
 downstream agent

Commands

python3 agent.py status
python3 agent.py sign --input envelope.json
python3 agent.py verify --input envelope.json --db webhook_guard.sqlite3
python3 agent.py run --once --inbox inbox --accepted accepted --quarantine quarantine
python3 agent.py run --llm-routing

verify exits 0 only for an accepted event, 2 for a rejected event, and 1 for local configuration or input errors.

Security boundaries

  • Secrets are read from an environment variable or file, never from CLI flags.
  • Comparisons use hmac.compare_digest.
  • The replay check and insert occur under BEGIN IMMEDIATE.
  • Invalid events never create replay-ledger entries.
  • The body is never sent to an LLM.
  • Nothing in this pack opens a network socket or executes webhook content.
  • Quarantine is an audit destination, not a retry queue.

Start with CHECKLIST.md, then adapt the gateway and source mappings in CUSTOMIZE.md.

Use a local model (Ollama)

The optional LLM step can also run on your own machine through Ollama, so no text leaves your server. No account and no API key.

ollama pull <model>               # any chat model you like
export PACK_LLM=ollama            # prefer Ollama even if a coding-agent CLI is installed
export PACK_OLLAMA_MODEL=<model>  # optional: default is the first chat model the server lists
python3 -c "import llm_adapter; print(llm_adapter.detect())"   # prints: ollama
  • PACK_OLLAMA_HOST points to another server (default http://127.0.0.1:11434); OLLAMA_HOST is also read when PACK_LLM=ollama.
  • PACK_OLLAMA_NUM_CTX sets the context size if your reports or prompts are long.
  • Without PACK_LLM, a local Ollama server is used only when no Claude Code, Kimi Code or Codex CLI is found.
  • If the server is down or the model is missing, the pack keeps working in its deterministic mode.

Learning memory (local and private)

Every LLM call this pack makes is journaled in ./.pack_memory/ next to the code, so the pack can improve for you. Nothing is ever sent anywhere.

  • What is kept: the task name, the masked input, the masked answer, status and latency. Keys, tokens, e-mails, phone numbers, IP addresses, card and IBAN numbers and personal paths are masked before writing; the system prompt is never stored.
  • Teach it: python3 pack_memory.py feedback last ok confirms a result, python3 pack_memory.py feedback last bad --fix "the right answer" corrects it. Confirmed and corrected runs are reused as validated examples when a similar input comes in. Unrated runs are never injected into a prompt.
  • Find similar cases: python3 pack_memory.py similar "some text" and python3 pack_memory.py clusters (groups of near-identical failures) work in plain Python, no model needed. Optional meaning-based matching uses an embedding model you already run locally: python3 pack_memory.py enable-semantic (Ollama on this machine only).
  • Share, only if you want: python3 pack_memory.py export writes an anonymised summary (counts, statuses, size buckets, no text) for you to review.
  • Control: PACK_MEMORY=0 turns it off, PACK_MEMORY_STORE_TEXT=0 keeps fingerprints only, python3 pack_memory.py purge --yes wipes it.