This document describes the security mechanisms implemented in the AGS API MCP Server.
All Bearer tokens are cryptographically verified using JWKS (JSON Web Key Set):
- Discovery: The JWKS URI is fetched from
{agsBaseUrl}/.well-known/oauth-authorization-server - Verification: Token signatures are validated against the public keys from the JWKS endpoint
- Algorithm: Only RS256 is accepted (prevents algorithm confusion attacks)
- Issuer Validation: The token's
issclaim is verified against the expected AGS base URL - Expiration: Tokens must be within their
expwindow (30s clock tolerance) - Audience: Optional
audclaim validation (configurable via middleware options)
Invalid or forged tokens receive a 401 Unauthorized response.
To minimize latency and reduce load on the authorization server:
- JWKS URI discovery is cached for 10 minutes (configurable via
JWKS_CACHE_TTL_MS) - Signing keys are cached for 10 minutes (configurable via
JWKS_CACHE_MAX_AGE) - Rate limiting: Maximum 10 JWKS requests per minute (configurable via
JWKS_RATE_LIMIT) - Cache size: Limited to 50 entries to prevent unbounded memory growth
- Pre-warming: JWKS cache is pre-warmed on server startup when auth is enabled
In hosted (multi-tenant) mode, the AGS base URL is derived from the request's Host header. Additional protections include:
- Token issuer is validated against the derived host URL
- Mismatched issuers return
403 Forbidden
The serverUrl parameter has been removed from the run-apis tool across all implementations (V1 HTTP, V1 stdio, V2 MCP). Server URLs now come from:
- OpenAPI specification
serversdefinitions AB_BASE_URLenvironment variable (fallback)
As defense-in-depth, all outbound requests are checked against private/internal IP ranges:
IPv4:
127.0.0.0/8(loopback)10.0.0.0/8(RFC 1918 Class A)172.16.0.0/12(RFC 1918 Class B)192.168.0.0/16(RFC 1918 Class C)169.254.0.0/16(link-local / cloud metadata)100.64.0.0/10(CGNAT)198.18.0.0/15(benchmarking)0.0.0.0(unspecified)255.255.255.255(broadcast)
IPv6:
::1(loopback)fe80::/10(link-local)fc00::/7(unique local)fd00::/8(unique local)- IPv4-mapped IPv6 (
::ffff:x.x.x.x) — normalized and checked against IPv4 patterns
Hostnames:
localhostand*.localhostmetadata.google.internal(GCP metadata service)metadata.azure.com(Azure metadata service)
Blocked requests are logged with event: "ssrf_blocked" for security monitoring.
Hostnames are resolved via dns.resolve4/dns.resolve6 and all resolved IP addresses are validated against the private IP patterns before the HTTP request is made. This prevents attackers from returning a public IP during initial validation and a private IP when the actual connection is established.
Structured security events are logged for monitoring and incident response:
| Event | Level | Description |
|---|---|---|
auth_failure |
WARN | Authentication failure (invalid token, expired, wrong issuer) |
auth_success |
DEBUG | Successful authentication |
suspicious_request |
WARN | Suspicious activity (issuer mismatch, missing claims) |
rate_limit_exceeded |
WARN | Rate limit threshold exceeded |
ssrf_blocked |
WARN | Outbound request to private/internal address blocked |
All security events include the client IP address. Configure TRUST_PROXY when behind a reverse proxy to ensure accurate IP logging.
Security events are emitted as structured JSON logs. Forward them to your log aggregation/SIEM system and configure alerts for the following conditions:
| Alert | Filter | Threshold | Action |
|---|---|---|---|
| High auth failure rate | event = "auth_failure" |
> 10% of total auth requests over 5 min | Investigate for credential stuffing or misconfigured clients |
| SSRF attempts detected | event = "ssrf_blocked" |
Any occurrence (should be rare) | Review source IP, inspect request patterns |
| Suspicious requests | event = "suspicious_request" |
> 5 events from single IP in 1 min | Review auth logs, consider IP blocking |
| Rate limit exceeded | event = "rate_limit_exceeded" |
Frequent from legitimate users | Review rate limit configuration |
Log aggregation filter:
event IN ("auth_failure", "ssrf_blocked", "suspicious_request", "rate_limit_exceeded")
Dashboard suggestions:
- Event counts by type over time
- Top source IPs by auth failure count
- Auth failure reasons breakdown (expired, wrong issuer, invalid signature)
- Configurable via
RATE_LIMIT_ENABLED,RATE_LIMIT_WINDOW_MINS, andRATE_LIMIT_MAX - Defaults: 1000 requests per 15-minute window per IP
- Rate limit violations are logged as security events
Helmet middleware provides:
Content-Security-PolicyStrict-Transport-Security(HSTS)X-Frame-OptionsX-Content-Type-Options- And other standard security headers
serverUrlparameter removed: ConfigureAB_BASE_URLenvironment variable instead- JWT verification enforced: Only AGS-signed tokens are accepted; forged/unsigned tokens now return 401
- Auth failures block requests: Previously, invalid tokens were silently ignored; now they return 401
Before:
await tools.runApi({
spec: 'iam',
method: 'get',
path: '/users',
serverUrl: 'https://dev.accelbyte.io' // User-specified
});After:
# Set via environment variable
AB_BASE_URL=https://dev.accelbyte.ioawait tools.runApi({
spec: 'iam',
method: 'get',
path: '/users'
// serverUrl parameter removed — uses AB_BASE_URL
});Ensure your client sends valid AGS-issued Bearer tokens. Tokens must:
- Be signed with RS256
- Have a valid
issclaim matching yourAB_BASE_URLenvironment - Not be expired (30s clock tolerance is allowed)
- Set
AB_BASE_URLin your environment - Remove all
serverUrlparameters from API calls - Verify API calls succeed with a valid Bearer token
- Confirm
401 Unauthorizedis returned for invalid/missing tokens
See ENVIRONMENT_VARIABLES.md for configuration details.