The plugin sits across three threat-rich domains:Documentation Index
Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
Use this file to discover all available pages before exploring further.
- MCP server security (OWASP MCP Top 10 2026, CVE-2025-6514, the Anthropic Git MCP CVE chain CVE-2025-68143/144/145)
- Indirect prompt injection (Anthropic’s Feb 2026 system card identifies indirect injection as the dominant attack class)
- Outbound HTTP from a server-side runtime (SSRF surged 452% from 2023 to 2024)
Five-gate authorization stack
Everyexecute_action traverses five gates. Removing any of them requires editing the plugin source:
Bundle signing (OPA model)
Theintegrity envelope on every bundle is a JWT-of-hashes modeled on OPA’s signed-bundle pattern.
- Re-compute sha256 of the canonicalized bundle minus the
integrityfield. - Reject if the computed digest doesn’t match
integrity.digest. - Look up the trusted public key by
integrity.keyId. Reject if unknown. - Reject if the trusted key’s algorithm doesn’t match
integrity.alg. - Verify the signature over the canonical bytes (not the digest hex) using the public key.
/healthz + structured error log + audit trail. Never partial apply.
dev: true bypasses signature verification with a loud startup warning. Never set it in production. Setting requireSignature: false without dev: true also emits a startup warning to call out the misconfiguration.
RFC 8707 enforcement
Per the MCP authorization spec (draft 2026-03-15), every JWT must carry RFC 8707 Resource Indicators. The plugin enforces this on the SaaS-push channel viaBundlePushJwtVerifier:
audmust equal the configuredexpectedAudience(typicallybundleId)resourcemust equal the configuredexpectedResource(typically the FrontMCP server’s canonical URL)issmust equalexpectedIssuerrolesmust include at least one ofrequiredRoles(default:['frontmcp:cloud:push'])
passthrough caller token outbound calls (an AuthBinding with passthroughCallerToken: true), the plugin verifies the caller’s JWT has a resource claim matching the customer REST API’s base URL before forwarding it.
Input sanitization (CVE-2025-6514 lesson)
Even after signature verification, every string in a bundle is treated as adversarial input. The Zod schema inbundle.schema.ts enforces:
- URL construction: WHATWG
URLonly, never string concat - Path templates: reject
.., backticks,$(,${, whitespace,?,# - Header names: must match RFC 7230 token grammar
- Header values: stripped of CR/LF before assignment
- JSON Schema input:
additionalPropertiesconstraints applied at the Zod boundary; the executor validates input against the schema again before invoking the upstream - No
eval,Function,child_process,vm, or dynamicrequireanywhere in the executor or sync paths - No bundle data in shell commands — the plugin never shells out
SSRF defenses (layered)
A hostname allowlist alone is insufficient (DNS rebinding bypasses it). The plugin layers:- URL string check before DNS resolution: reject
file:,data:,gopher:,ftp:; requirehttps:(allowhttp:only via explicitallowHttp: true). - Hostname allowlist: only declared
services[].baseUrlhosts. - Cloud-metadata hostname blocklist:
metadata.google.internal,metadata.azure.com,metadata.aws.comblocked even if technically allowlisted. - DNS resolve once at request start; reject if any resolved IP falls in deny ranges:
- RFC 1918 private (10/8, 172.16/12, 192.168/16)
- Link-local (169.254/16) — blocks AWS/GCP/Azure metadata services (169.254.169.254)
- Loopback (127/8, ::1)
- IPv6 ULA (fc00::/7) and link-local (fe80::/10)
- Per-host concurrency cap (
maxConcurrencyPerHost, default 10).
allowPrivateNetworks: true bypasses the IP blocklist for self-hosted scenarios where the customer’s REST API legitimately lives on a private network. Loud startup warning when set. Default is fail-closed.
ABAC integration
requiredAuthorities on a skill or operation is the same AuthoritiesPolicy shape @frontmcp/auth uses everywhere:
AuthoritiesEngine from @frontmcp/auth — RBAC, ABAC, ReBAC, custom evaluators, and combinators (anyOf, allOf, not) are all available. See the authorities docs for the full grammar.
The check happens inside execute_action, with the caller’s authInfo from the MCP request mapped through AuthoritiesContextBuilder. Denial returns ok: false with a structured reason and is recorded in the audit log.
OWASP MCP Top 10 (2026) coverage
| OWASP MCP risk | This plugin’s defense |
|---|---|
| MCP-1 Tool poisoning | Bundle signing + bundle-diff log on every swap (rug-pull detection) |
| MCP-2 Prompt injection (direct) | Inbound auth + meta-tool input schema strict validation |
| MCP-3 Indirect prompt injection | Structural output envelope from execute_action; output schema enforcement |
| MCP-4 Excessive agency | Per-skill ABAC + credential scoping + structured ABAC denial path |
| MCP-5 Sensitive info disclosure | Outbound allowlist + audit log + no credential echo in logs/traces |
| MCP-6 Insecure tool description | Signed-bundle origin + bundle-diff log surfaces description changes |
| MCP-7 Confused deputy | RFC 8707 enforcement on every JWT (push channel + caller-token passthrough) |
| MCP-8 Supply chain | Sigstore/SLSA provenance verification on npm-source (planned v1.2.x) |
| MCP-9 Excessive permissions | Per-bundle credential allowlist + per-skill scoping |
| MCP-10 Insufficient observability | OTel spans + audit log + bundle diff + /healthz enrichment |
Indirect prompt injection mitigations
The customer’s REST API can return content (CRM names, ticket bodies, user-supplied fields) that contains instructions targeting the LLM. Defenses:- Structural separation:
execute_actionreturns{ ok, status, data, contentType, error }—datais opaque to the LLM unless explicitly extracted. - Output schema validation as a bottleneck: every response validated against the bundle’s declared
outputSchema. - Response size cap (
defaultMaxResponseBytes, default 256KB; per-op override viaop.maxResponseBytes). - Optional content sanitizer hook (planned v1.2.x): customers register a
sanitize(action, data)callback to strip HTML, redact PII, or refuse content matching known-injection heuristics.
Production checklist
Before flipping to production:-
dev: false(default) andrequireSignature: true(default) - At least one entry in
trustedKeys[] - Credentials wired via the auth vault instead of the in-memory
MemoryCredentialResolverfor any non-trivial deployment -
allowHttp: false(default) unless your upstream is onlocalhost -
allowPrivateNetworks: false(default) unless self-hosted on a private network -
outbound.maxConcurrencyPerHosttuned to match your upstream’s rate-limit budget - Audit log routed to a SIEM
-
/healthzpolling configured to alert onlastPullAt > 2 * pollIntervalMs(saas source) orlastApplyError != null
Deferred to v1.2.x
The following are planned but not in v1.2.0:- Sigstore Cosign v3 keyless verification (alongside the JWT/key path)
- GitHub artifact attestation verification for npm-source
- Webhook channel (
POST /__skilled_openapi/push) with nonce + replay window - Optional content-sanitizer hook for response bodies
- Discovery-bundle pattern for signing key rotation
- Bundle rollback admin endpoint (
POST /__skilled_openapi/rollback) - Per-service circuit-breaker (currently relies on per-host concurrency cap)
- TLS pinning per service (opt-in)