Guidance for coding agents running or changing this repo. Read README.md first: it explains the webhook, the policy
format and the test flow. The nested articles build on it: aigw-byo-guardrail-mcp/
(the webhook as an MCP guardrail, with the probe MCP server), aigw-mcp-airs-webhook/
(Prisma AIRS scans of MCP tool calls through the webhook), aigw-mcp-descriptor-pinning/
(deny new or changed tool descriptors) and aigw-mcp-tiered-tool-access/
(per-group tool access with the x-tool-policy allowlist).
- AWS (
terraform -chdir=infra apply): the webhook Lambda, its IAM role, CloudWatch log group and a public Function URL (authorization_type = "NONE"; the shared token is the only protection). Withmcp_server = true, the same set again for the probe MCP server. Terraform state holds the webhook token, the MCP token and, with AIRS on, the AIRS API key. A plainapplywithout the variables used last time can destroy or change what's deployed: checkterraform planfirst. - Kubernetes (
k8s/webhook.sh): a Deployment, Service, Secrets ($NAME,$NAME-airs) and ConfigMaps ($NAME-code,$NAME-policy) in$K8S_NAMESPACE.deleteremoves exactly those. Check thatNAMEdoesn't clash with something already in the namespace. - Docker (
docker/mcp.sh): a probe MCP server container with network aliases on the gateway's Compose network. A webhook container needs a volume on/datafor the pin state. Changing the gateway's environment means adocker-compose.override.ymland a gateway recreate (the installer ignores extravalues.yamlkeys). CheckNAMEagainst existing containers. - SCM tenant (
tests/e2e.sh wire): guardrails, configs and service keys named$PREFIX-closed/$PREFIX-open(default prefixbyo-probe) in$WS_ID. Their ids and key secrets go to$STATE(default~/.local/state/aigw-byo-guardrail), whichteardownreads; don't delete it before teardown. The MCP articles also create MCP integrations, guardrail mappings and keys by hand (see their READMEs); name them*-probe-*or*-test-*. - Gateway: a webhook on a private host (k8s, Docker) only works once its host is in the hybrid gateway's
TRUSTED_CUSTOM_HOSTS. Changing that redeploys a shared data plane: get the operator's go-ahead first and revert it afterwards. Mapping a guardrail to an MCP server, or setting a workspace default, changes behavior for everyone using that server or workspace; the same applies. - Prisma AIRS (with
AIRSset): one sync scan per hook, logged in the tenant underapp_name.
- Secrets.
- Never print, log or commit the webhook token, MCP token, AIRS API key, SCM credentials, gateway keys or JWTs.
terraform output -raw webhook_tokenandk8s/webhook.sh tokengo into a variable, not the terminal. - When inspecting JSON from a CLI or API that may carry secrets, select known-safe fields (
jq '{id, name}', orjq 'keys'first). Don't rely on deleting the field names you expect: the secret field may be named differently. - Pass the AIRS key as
TF_VAR_airs_api_keyor env, never in a tfvars file.LOG_PAYLOAD/log_payloadlogs prompts and tool content: testing only.
- Never print, log or commit the webhook token, MCP token, AIRS API key, SCM credentials, gateway keys or JWTs.
- Tenant data stays out of git.
infra/terraform.tfvars,infra/backend.tf,*.auto.tfvars, state files andCLAUDE.local.mdare gitignored; keep deployment facts there. Don't add tenant or TSG IDs, workspace or org UUIDs, account numbers, gateway or Function URLs, real IPs, profile names or customer names to tracked files. Use placeholders (<gateway>,<workspace uuid>,<your profile>). - Only touch what you created.
e2e.sh teardowndeletes the objects recorded in$STATE. Never delete guardrails, integrations, keys or profiles you didn't create; other people's objects may be in the same workspace. - Hybrid MCP mapping cache. After mapping or unmapping a guardrail on an MCP server, re-save the MCP integration or
the old mapping keeps running (see
aigw-byo-guardrail-mcp). Don't report a mapping change as tested until you've done that. - Report what you observe. A result counts only if the webhook log or the gateway response shows it. If a claim comes from docs or code reading rather than a run, say so.
- More secrets.
PIN_HEADERSholds a gateway key andAIRS_LIST_API_KEYis an AIRS key: never print the webhook's environment ordocker inspectoutput. WithPIN.airson, tool descriptions are sent to AIRS. - Read the webhook log. The client only sees
Webhook request failed(JSON-RPC-32446). The reason (pin_driftkindchanged,neworairs-blocked, or "penalty box") is only in the webhook log. A denial isn't understood until you've read it there. - Trust on first use baselines whatever the server serves on the first call. Inspect
tools/listbefore pointing pinning at a server you didn't deploy. Don't runpinctl approveto make a test pass on a server someone else owns; approving is the operator's decision. This repo hasn't shown thatapprove --hashis enforced, so don't claim it. - Timing. The webhook re-reads descriptors every
refresh_s(default 30): wait longer than that after changing the probe, or the call is judged against the old list. - Workspace defaults. Whether
workspaces update --defaultsmerges with or replaces existingdefaultsis untested. Read the workspace's currentdefaultsand save them before settingmcp_input_guardrails, and put them back at the end. A default takes effect without an integration re-save (unlike a mapping). - Pin state. Don't delete
/data/pins.jsonor the volume on a deployment you didn't create: it resets every server to trust on first use.
- Remove MCP guardrail mappings you set and re-save the integration. Restore any workspace defaults you changed (no re-save needed).
bash tests/e2e.sh teardown(samePREFIX,STATEandMODESaswire); delete any MCP integrations, guardrails and keys you created by hand.- Revert
TRUSTED_CUSTOM_HOSTS(the Compose override on Docker) if you changed it. bash k8s/webhook.sh delete,bash docker/mcp.sh delete(plus the webhook container and its volume) and/orterraform -chdir=infra destroy, unless the operator wants it kept.
webhook/andmcp/are stdlib-only Python; shell scripts are bash withset -euo pipefail. Keep it that way: no new dependencies, so the Lambda zip and the k8s ConfigMap stay plain source files.- A new file under
webhook/has to be added in three places: thearchive_filesources ininfra/main.tf, the code ConfigMap ink8s/webhook.sh, and the README's file table. Exception:pinctl.pyisn't in the Lambda zip, andPINhas no Terraform variable, since descriptor pinning refuses to run on Lambda (no shared state file). - Descriptor pinning keeps state in a file (
PINstore, default/data/pins.json). Deleting it, or a pod losing its emptyDir, resets every server to trust on first use. Treat it like the other tenant data: never commit it. - New webhook settings go in env, documented in the module docstring,
infra/variables.tf,k8s/webhook.shand the article that uses them. - Run
python3 -m unittest discover -s testsbefore committing; the AIRS tests use a fake poster and never call AIRS. - If you rerun a test against a new gateway or AIRS version, add the version and date to that article's Findings, not over the old results.