Configuration
iron-proxy is configured via a single YAML file, passed at startup with the -config flag:
iron-proxy -config /etc/iron-proxy/proxy.yamlBelow is a complete reference for every configuration option.
Full Example
dns:
listen: ":53"
proxy_ip: "172.20.0.2"
upstream_resolver: "8.8.8.8:53"
passthrough:
- "*.internal.corp"
records:
- name: "custom.local"
type: A
value: "10.0.0.5"
proxy:
http_listen: ":80"
https_listen: ":443"
tunnel_listen: ":1080"
max_request_body_bytes: 1048576
max_response_body_bytes: 0
upstream_response_header_timeout: "30s"
upstream_deny_cidrs:
- "169.254.169.254/32"
- "fd00:ec2::254/128"
- "127.0.0.0/8"
- "::1/128"
tls:
mode: "mitm"
ca_cert: "/certs/ca.crt"
ca_key: "/certs/ca.key"
cert_cache_size: 1000
leaf_cert_expiry_hours: 72
transforms:
- name: allowlist
config:
domains:
- "registry.npmjs.org"
cidrs:
- "10.0.0.0/8"
rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
- host: "*.anthropic.com"
methods: ["POST"]
paths: ["/v1/messages", "/v1/complete"]
- name: secrets
config:
secrets:
- source:
type: env
var: OPENAI_API_KEY
inject:
header: "Authorization"
formatter: "Bearer {{ .Value }}"
rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
- source:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-west-1:123456789:secret:anthropic-key"
region: "us-west-1"
ttl: 5m
replace:
proxy_value: "pk-proxy-anthropic-xyz"
match_headers: ["x-api-key"]
require: true
rules:
- host: "api.anthropic.com"
- source:
type: aws_ssm
name: "/myapp/openai-key"
region: "us-east-1"
ttl: 15m
replace:
proxy_value: "pk-proxy-openai-param"
match_headers: ["Authorization"]
rules:
- host: "api.openai.com"
- name: annotate
config:
annotations:
- rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
headers: ["x-request-id"]
- name: grpc
config:
name: "policy-engine"
target: "localhost:9500"
send_request_body: true
send_response_body: true
rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
tls:
enabled: true
ca_cert: "/etc/iron-proxy/grpc-ca.pem"
cert: "/etc/iron-proxy/grpc-client.pem"
key: "/etc/iron-proxy/grpc-client-key.pem"
- name: judge
config:
name: "github-write-guard"
fallback: "deny"
timeout: "8s"
rules:
- host: "api.github.com"
methods: ["POST", "PATCH", "DELETE", "PUT"]
provider:
type: "anthropic"
model: "claude-haiku-4-5-20251001"
api_key_env: "ANTHROPIC_API_KEY"
prompt: |
Allow writes to the repository under review. Deny writes to user
settings, billing, or any other repository.
- name: header_allowlist
config:
headers:
- "Authorization"
- "Content-Type"
- "User-Agent"
- "Accept"
- "/^X-Trace-.*$/"
rules:
- host: "api.openai.com"
mcp:
error:
code: -32001
message: "blocked by iron-proxy policy"
servers:
- name: github
rules:
- host: "mcp.github.com"
paths: ["/mcp", "/mcp/*"]
tools:
- name: "search_repositories"
- name: "create_issue"
when:
- path: "owner"
equals: "ironsh"
- path: "repo"
in: ["iron-proxy"]
management:
listen: "127.0.0.1:9092"
api_key_env: "IRON_MANAGEMENT_API_KEY"
metrics:
listen: ":9090"
log:
level: "info"dns
Configures the built-in DNS server. The DNS server returns proxy_ip for all lookups so that outbound traffic routes through the proxy.
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":53" | Address and port the DNS server binds to. |
proxy_ip | string | required | IP address where iron-proxy is running. All DNS responses resolve to this IP. |
upstream_resolver | string | OS default | Upstream DNS resolver address (e.g., "8.8.8.8:53"). When set, both passthrough DNS queries and upstream HTTP connections resolve via this server instead of the OS default. Useful when iron-proxy owns the system DNS. |
passthrough | string[] | [] | Domain glob patterns that are forwarded to the upstream resolver instead of being intercepted. Useful for internal DNS names that should not route through the proxy. |
records | object[] | [] | Static DNS records. These take precedence over interception and passthrough. See below. |
dns.records[]
| Field | Type | Description |
|---|---|---|
name | string | Domain name for the record. |
type | string | Record type: A or CNAME. |
value | string | IP address (for A records) or target hostname (for CNAME records). |
proxy
Configures the HTTP/HTTPS proxy listeners.
| Field | Type | Default | Description |
|---|---|---|---|
http_listen | string | ":80" | Address and port for HTTP traffic. |
https_listen | string | ":443" | Address and port for HTTPS traffic. |
tunnel_listen | string | (disabled) | Address and port for the CONNECT/SOCKS5 tunnel listener. Accepts both HTTP CONNECT and SOCKS5 requests. See the SOCKS5 and CONNECT tunnels guide for details. |
max_request_body_bytes | integer | 1048576 (1 MiB) | Maximum request body size that the proxy will buffer. Bodies are only buffered when a transform needs to inspect them. |
max_response_body_bytes | integer | 0 (unlimited) | Maximum response body size that the proxy will buffer. Set to 0 to disable the limit. |
upstream_response_header_timeout | duration | "30s" | Maximum time to wait for an upstream to begin sending response headers. Raise this for slow upstreams (e.g., long-running LLM calls). Invalid or non-positive durations are rejected at startup. |
upstream_deny_cidrs | string[] | IMDS + loopback (see below) | CIDR ranges the proxy will refuse to dial regardless of allowlist contents. Enforced after DNS resolution, so hostnames that resolve into a denied range are refused before TCP connect. Set to [] to opt out. |
upstream_deny_cidrs Defaults
When upstream_deny_cidrs is unset, iron-proxy blocks cloud instance metadata and loopback by default:
169.254.169.254/32(AWS / GCP / Azure IPv4 IMDS)fd00:ec2::254/128(AWS IPv6 IMDS)127.0.0.0/8(IPv4 loopback)::1/128(IPv6 loopback)
The defaults intentionally omit RFC1918 ranges, since many iron-proxy deployments target private corporate networks.
Provide an explicit list to override the defaults entirely, or an empty list to opt out:
proxy:
upstream_deny_cidrs:
- "169.254.169.254/32"
- "127.0.0.0/8"If a workload legitimately needs to talk to IMDS or loopback through the proxy, override the list before upgrading or the proxy will refuse those connections.
tls
Configures how iron-proxy handles HTTPS traffic. Two modes are supported: mitm (the default), which terminates TLS using a CA you provide and mints leaf certificates on the fly, and sni-only, which passes TLS through without termination.
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | "mitm" | TLS handling mode. One of mitm or sni-only. See TLS modes below. |
ca_cert | string | required for mitm | Path to the CA certificate file (PEM format). Not used in sni-only mode. |
ca_key | string | required for mitm | Path to the CA private key file (PEM format). Not used in sni-only mode. |
cert_cache_size | integer | 1000 | Number of generated leaf certificates to keep in the LRU cache. Not used in sni-only mode. |
leaf_cert_expiry_hours | integer | 72 | Validity duration (in hours) for generated leaf certificates. Not used in sni-only mode. |
TLS Modes
mitm (default): iron-proxy terminates the client TLS connection, inspects the decrypted request, and opens a new TLS connection to the upstream server. Clients must trust iron-proxy's CA certificate. This is the only mode that lets transforms see request methods, paths, headers, and bodies.
sni-only: iron-proxy peeks at the TLS ClientHello SNI and TCP-passthroughs the connection to the upstream without terminating TLS. Clients do not need to trust a proxy CA. The transform pipeline still runs with a host-only synthetic request: method, path, headers, and body are empty, so host-based allowlist rules are the only things that can match. Body-inspecting transforms like secrets and grpc still run but have nothing to act on. The CONNECT/SOCKS5 tunnel's TLS branch also switches to passthrough in sni-only mode.
tls:
mode: "sni-only"Use sni-only when you need host-level egress control but cannot distribute a CA certificate to workloads. Use mitm when you need secret injection, body inspection, or method/path allowlists.
transforms
An ordered array of transforms that run on every request. All transforms must pass for the request to be forwarded upstream. Transforms execute in the order they appear in the configuration.
Each transform has a name and a config object. The available transforms are documented below.
allowlist
Controls which destinations are reachable through the proxy. Requests to destinations not in the allowlist receive an HTTP 403 response.
There are two ways to specify allowed destinations: flat lists (domains and cidrs) that allow all methods and paths, and rules that support method and path restrictions. Both can be used together in the same allowlist.
| Field | Type | Default | Description |
|---|---|---|---|
domains | string[] | [] | Hostname glob patterns to allow (e.g., registry.npmjs.org, *.anthropic.com). A bare "*" matches any host as a catch-all. All methods and paths are permitted. |
cidrs | string[] | [] | CIDR ranges to allow (e.g., 10.0.0.0/8). All methods and paths are permitted. |
rules | object[] | [] | Rules with optional method and path restrictions. See below. |
warn | boolean | false | When true, violations are logged but not blocked. Useful for rolling out allowlists incrementally. |
allowlist.rules[]
Each rule matches a single host or CIDR, with optional method and path filters. A request is allowed if it matches any rule (or any flat domains/cidrs entry).
| Field | Type | Default | Description |
|---|---|---|---|
host | string | Hostname glob pattern. A bare "*" matches any host. Mutually exclusive with cidr. | |
cidr | string | CIDR range. Mutually exclusive with host. | |
methods | string[] | all | HTTP methods to allow (e.g., ["GET", "POST"]). Omit or set to ["*"] to allow all methods. |
paths | string[] | all | Path patterns to allow (e.g., ["/v1/*"]). Must start with /. Supports * wildcards. Omit to allow all paths. |
secrets
Injects or replaces secret values at the egress boundary so that real credentials are never exposed to sandboxed workloads. Each secret declares its own source and either an inject or replace block.
See the Static Secrets reference for a full overview of inject and replace modes, secret sources, and credential rotation. The fields below are the canonical schema.
| Field | Type | Default | Description |
|---|---|---|---|
secrets | object[] | [] | List of secret entries. See below. |
secrets.secrets[]
| Field | Type | Default | Description |
|---|---|---|---|
source | object | required | Where to read the secret value. Contains a type field (env, aws_sm, aws_ssm, 1password, or 1password_connect) plus type-specific fields. See sources below. |
inject | object | Inject the secret onto matching requests unconditionally. See inject mode. Mutually exclusive with replace. | |
replace | object | Replace a proxy token with the real value. See replace mode. Mutually exclusive with inject. | |
rules | object[] | [] | Restrict this secret to specific destinations. Uses the same format as allowlist.rules[]. If empty, the secret applies to all destinations. |
Secret Sources
Every source supports an optional json_key field. When set, the resolved value is parsed as JSON and the named field is extracted before use. This works with all source types (env, aws_sm, aws_ssm, 1password, 1password_connect) and is useful for pulling individual fields out of a single shared JSON secret.
env
Read the secret from an environment variable on the iron-proxy process.
| Field | Type | Description |
|---|---|---|
type | string | Must be env. |
var | string | Environment variable name containing the real secret value. |
json_key | string | Optional. When set, parse the value as JSON and extract this field. |
source:
type: env
var: OPENAI_API_KEYaws_sm
Read the secret from AWS Secrets Manager. The value is cached and refreshed in the background based on the configured TTL.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be aws_sm. | |
secret_id | string | required | Secret ARN or name in AWS Secrets Manager. |
region | string | AWS SDK default | AWS region where the secret is stored. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 (no refresh) | Re-fetch interval. Set to 0 to read the value once at startup. |
source:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-west-1:123456789:secret:my-key"
region: "us-west-1"
ttl: 10maws_ssm
Read the secret from AWS Systems Manager Parameter Store. Like aws_sm, values are cached and refreshed in the background based on the TTL.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be aws_ssm. | |
name | string | required | Parameter name or ARN. |
region | string | AWS SDK default | AWS region where the parameter is stored. |
with_decryption | boolean | true | Decrypt SecureString parameters. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 (no refresh) | Re-fetch interval. Set to 0 to read the value once at startup. |
source:
type: aws_ssm
name: "/myapp/api-key"
region: "us-east-1"
with_decryption: true
ttl: 15miron-proxy uses the standard AWS credential chain (environment variables, instance profile, ECS task role, etc.) to authenticate with AWS.
1password
Read the secret from 1Password using a service account token. Like the AWS sources, values are cached and refreshed in the background based on the TTL. For most deployments, prefer 1password_connect: the hosted SDK service applies per-account rate limits that can stall request handling under load.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be 1password. | |
secret_ref | string | required | 1Password reference using the op://vault/item/[section/]field syntax. |
token_env | string | OP_SERVICE_ACCOUNT_TOKEN | Environment variable holding the 1Password service account token. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 (no refresh) | Re-fetch interval. Set to 0 to read the value once at startup. |
source:
type: 1password
secret_ref: "op://Engineering/OpenAI/credential"
token_env: OP_SERVICE_ACCOUNT_TOKEN
ttl: 15m1password_connect
Read the secret from a 1Password Connect server running in your own infrastructure. This is the recommended 1Password integration: Connect runs locally and avoids the per-account rate limits that the hosted SDK enforces. Values are cached and refreshed in the background based on the TTL.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be 1password_connect. | |
secret_ref | string | required | 1Password reference using the op://vault/item/[section/]field syntax. |
host_env | string | OP_CONNECT_HOST | Environment variable holding the Connect server URL. |
token_env | string | OP_CONNECT_TOKEN | Environment variable holding the Connect API token. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 (no refresh) | Re-fetch interval. Set to 0 to read the value once at startup. |
source:
type: 1password_connect
secret_ref: "op://Engineering/OpenAI/credential"
host_env: OP_CONNECT_HOST
token_env: OP_CONNECT_TOKEN
ttl: 15mInject Mode
In inject mode, the proxy unconditionally sets a header or query parameter on every request that matches the secret's rules. The workload never sees or sends any credential. This is useful when sandboxed workloads should have no knowledge of credentials at all.
| Field | Type | Description |
|---|---|---|
header | string | Header name to set on matching requests. Sent upstream with the exact casing written here (HTTP/1.x only: HTTP/2 lowercases header names regardless). Mutually exclusive with query_param. |
query_param | string | Query parameter name to set on matching requests. Mutually exclusive with header. |
formatter | string | Go template for the header value. Receives .Value (the resolved secret) and a base64 helper. Not used with query_param. |
The formatter field supports Go templates. Use {{ .Value }} to insert the raw secret. The base64 helper concatenates and base64-encodes its arguments:
# Set Authorization: Bearer <secret>
inject:
header: "Authorization"
formatter: "Bearer {{ .Value }}"
# Set a query parameter (no formatter needed)
inject:
query_param: "key"Replace Mode
In replace mode, the workload sends a proxy token that the proxy swaps for the real value before forwarding upstream.
| Field | Type | Default | Description |
|---|---|---|---|
proxy_value | string | required | Token that the sandboxed environment sends. The proxy replaces this with the real value before forwarding. |
match_headers | string[] | [] | Header names to scan for the proxy token. An empty list scans all headers. Each entry is either a literal header name (case-insensitive) or a /regex/ pattern compiled at config time as a case-insensitive regular expression matched against canonical header names. Literal and regex entries can be mixed. Headers are forwarded upstream with the exact casing written here (HTTP/1.x only: HTTP/2 lowercases header names regardless). |
match_body | boolean | false | When true, scan the request body for the proxy token. |
match_path | boolean | false | When true, scan req.URL.Path for the proxy token and swap in the resolved secret. Off by default because URL paths often appear in access logs on either side of the proxy. |
match_query | boolean | false | When true, scan the URL query string for the proxy token and swap in the resolved secret. Off by default because query strings often appear in access logs. |
require | boolean | false | When true, requests matching a configured rule are rejected with HTTP 403 if the proxy token is not present in any scanned location. Prevents workloads from bypassing secret management. |
The proxy_value, match_headers, match_body, match_path, match_query, and require fields may also be set at the top level of a secret entry for backwards compatibility. New configurations should use the replace block.
gcp_auth
Mints short-lived GCP OAuth2 access tokens from a service account keyfile and injects them as Authorization: Bearer on matching requests. Token minting, caching, and refresh are handled automatically. Requires MITM mode.
iron-proxy also stubs Google's OAuth2 token endpoints (oauth2.googleapis.com/token and the GCE/GKE metadata server's service-account token endpoints) so client SDKs that complete their own token dance reach the proxy with a placeholder token. The real token is minted separately and swapped onto the upstream API call.
See the GCP Service Accounts reference for a full overview, including keyfile sourcing, domain-wide delegation, and metadata server stubbing.
| Field | Type | Default | Description |
|---|---|---|---|
keyfile_path | string | Path to a Google service account JSON keyfile on disk. | |
keyfile | object | Secret source that resolves to the keyfile JSON. Uses the same shape as a secret source. | |
credentials_provider | object | Resolves credentials through the Google Cloud default chain (GKE Workload Identity, GOOGLE_APPLICATION_CREDENTIALS, Workload Identity Federation). See Workload Identity. | |
subject | string | Workspace user email to impersonate via domain-wide delegation. When set, the minted token acts as the subject rather than the service account. Incompatible with credentials_provider. | |
scopes | string[] | required | OAuth2 scopes to request. Baked into the minted token. |
rules | object[] | [] | Restrict the transform to specific destinations. Uses the same format as allowlist.rules[]. |
Exactly one of keyfile_path, keyfile, or credentials_provider must be set.
credentials_provider accepts {type: workload_identity}. The proxy holds the rotating credentials minted by the cloud SDK; the workload runs against the stubbed metadata server as usual.
- name: gcp_auth
config:
keyfile:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-east-1:123456789:secret:gcp-sa-key"
subject: "user@workspace.example.com"
scopes:
- "https://www.googleapis.com/auth/cloud-platform"
rules:
- host: "*.googleapis.com"oauth_token
Mints short-lived OAuth2 access tokens and injects them as Authorization: Bearer on matching requests. Each entry under tokens declares a grant type, its credential fields, and the hosts it applies to. Token exchange, caching, refresh, and single-flight deduplication are handled automatically. Requires MITM mode. If token minting fails the request is rejected with HTTP 502.
Each configured token_endpoint is also stubbed, so sandboxed client SDKs can complete their own OAuth2 handshake against the proxy with a placeholder token while the proxy injects the real one upstream.
See the OAuth2 Token Injection reference for a full overview, including per-vendor recipes (DocuSign, Salesforce, Box, Zoom, Gmail, AlphaSense) and credential rotation behavior.
| Field | Type | Default | Description |
|---|---|---|---|
tokens | object[] | required | List of token entries. At least one is required. See below. |
oauth_token.tokens[]
| Field | Type | Default | Description |
|---|---|---|---|
grant | string | required | One of refresh_token, client_credentials, password, jwt_bearer. |
token_endpoint | string | required | URL the proxy POSTs to in order to exchange credentials for an access token. Also stubbed for client-side token requests. |
scopes | string[] | [] | OAuth2 scopes requested at the token endpoint. |
rules | object[] | required | Destinations this entry applies to. Uses the same format as allowlist.rules[]. At least one rule is required. |
header | string | Authorization | Header to set on matching requests. |
value_prefix | string | Bearer | Prefix prepended to the token in the injected header value. |
token_endpoint_headers | object | Map of header name to secret source. Each resolved value is sent on the token POST itself, for vendors that require an API key alongside the standard form-body client auth. Header casing is preserved on the wire. |
Required credential fields depend on the grant:
| Grant | Required credentials | Optional credentials | Other required |
|---|---|---|---|
refresh_token | refresh_token, client_id | client_secret | |
client_credentials | client_id, client_secret | ||
password | username, password, client_id | client_secret | |
jwt_bearer | issuer, subject, private_key | private_key_id | audience |
Each credential field is a discrete secret source. private_key for jwt_bearer must resolve to a PEM-encoded RSA private key; private_key_id, when set, is emitted as the JWT kid header.
For Google service-account auth, use the gcp_auth transform instead: it wraps the same JWT-bearer flow with Google's keyfile format.
- name: oauth_token
config:
tokens:
- grant: refresh_token
refresh_token:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-east-1:123456789:secret:gsuite-oauth"
json_key: "refresh_token"
client_id:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-east-1:123456789:secret:gsuite-oauth"
json_key: "client_id"
client_secret:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-east-1:123456789:secret:gsuite-oauth"
json_key: "client_secret"
token_endpoint: "https://oauth2.googleapis.com/token"
scopes:
- "https://www.googleapis.com/auth/gmail.readonly"
rules:
- host: "gmail.googleapis.com"
- grant: jwt_bearer
issuer: {type: env, var: DOCUSIGN_INTEGRATION_KEY}
subject: {type: env, var: DOCUSIGN_USER_GUID}
private_key:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-east-1:123456789:secret:docusign-private-key"
audience: "account.docusign.com"
token_endpoint: "https://account.docusign.com/oauth/token"
scopes: ["signature", "impersonation"]
rules:
- host: "*.docusign.net"aws_auth
Re-signs inbound AWS SigV4 requests with real credentials. The workload's AWS SDK signs with placeholders; iron-proxy reads the region and service from the inbound credential scope and re-signs with real credentials drawn from any secret source. One config entry covers every AWS service the client speaks to. Requires MITM mode.
See the AWS Request Signing reference for a full overview, including scope gating, body handling modes, and worked examples.
| Field | Type | Default | Description |
|---|---|---|---|
access_key_id | object | Secret source resolving to the real AWS access key ID. Required unless credentials_provider is set. | |
secret_access_key | object | Secret source resolving to the real AWS secret access key. Required unless credentials_provider is set. | |
session_token | object | Secret source resolving to a session token (STS, assumed roles). Omit for long-lived IAM users. | |
credentials_provider | object | Resolves credentials through the AWS SDK default chain (IRSA, EKS Pod Identity, IMDSv2). Mutually exclusive with access_key_id/secret_access_key. Accepts {type: workload_identity, region?: string}. See Workload Identity. | |
allowed_regions | string[] | any region | Allowlist of AWS regions. Requests with a credential scope outside this set are rejected with HTTP 403. |
allowed_services | string[] | any service | Allowlist of AWS services. Requests with a credential scope outside this set are rejected with HTTP 403. |
unsigned_payload | boolean | false | Send UNSIGNED-PAYLOAD as the payload hash instead of reading and hashing the body. Required for S3 multipart and similar streaming uploads. |
allow_chunked_body | boolean | false | Sign chunked-encoding bodies without length verification. |
rules | object[] | required | Destinations this transform applies to. Uses the same format as allowlist.rules[]. At least one rule is required. |
- name: aws_auth
config:
access_key_id: {type: env, var: AWS_ACCESS_KEY_ID}
secret_access_key: {type: env, var: AWS_SECRET_ACCESS_KEY}
allowed_regions: ["us-east-1", "eu-west-1"]
allowed_services: ["bedrock", "s3", "dynamodb"]
rules:
- host: "*.amazonaws.com"hmac_sign
Signs outbound requests with HMAC before forwarding them upstream. Computes a signature over a Go-template message derived from the request and injects the signature plus any auxiliary credentials into a configurable set of headers. The four signature enums (algorithm, key_encoding, output_encoding, timestamp.format) plus the message template cover most signing schemes. Requires MITM mode.
See the HMAC Request Signing reference for a full overview, including template variable details, body integrity rules, and worked examples.
Because a truncated body would produce an invalid signature, the transform enforces body integrity:
- Bodies whose buffered length falls short of
Content-Length(truncated byproxy.max_request_body_bytes) are rejected with HTTP 413. - Chunked bodies are rejected with HTTP 400 unless
allow_chunked_body: true.
| Field | Type | Default | Description |
|---|---|---|---|
timestamp.format | string | required | One of unix_seconds, unix_millis, unix_nanos, rfc3339. |
signature.algorithm | string | required | HMAC hash. One of sha256, sha512, sha1. |
signature.key_encoding | string | required | Encoding of the HMAC key before keying. One of raw, base64, hex. |
signature.output_encoding | string | required | Encoding of the computed signature. One of base64, hex. |
signature.message | string | required | Go template for the signed message. Available fields: .Timestamp, .Method, .Path, .PathWithQuery, .Query, .Host, .Body. |
credentials | object | required | Map of credential name to secret source. Must include secret (the HMAC key). Other entries are addressable from header templates as .Credentials.<name>. |
headers | object[] | required | Ordered list of {name, value} entries to inject. value is a Go template with .Timestamp, .Signature, and .Credentials.<name> available. Header casing is preserved on the wire. |
allow_chunked_body | boolean | false | Opt in to signing chunked-encoding bodies. |
rules | object[] | required | Destinations this transform applies to. At least one rule is required. |
- name: hmac_sign
config:
timestamp:
format: unix_seconds
signature:
algorithm: sha256
key_encoding: base64
output_encoding: base64
message: "{{.Timestamp}}{{.Method}}{{.PathWithQuery}}{{.Body}}"
credentials:
key: {type: env, var: FALCONX_API_KEY}
secret: {type: env, var: FALCONX_SECRET}
passphrase: {type: env, var: FALCONX_PASSPHRASE}
headers:
- {name: "FX-ACCESS-KEY", value: "{{.Credentials.key}}"}
- {name: "FX-ACCESS-SIGN", value: "{{.Signature}}"}
- {name: "FX-ACCESS-TIMESTAMP", value: "{{.Timestamp}}"}
- {name: "FX-ACCESS-PASSPHRASE", value: "{{.Credentials.passphrase}}"}
rules:
- host: "api.falconx.io"body_capture
Records decoded request bodies of matching requests onto the audit log under body_capture.request_body (and body_capture.request_body_truncated when the body exceeds the cap). Observation-only: it never rejects a request, and read errors are annotated on the trace rather than failing the request. Response bodies are not captured.
| Field | Type | Default | Description |
|---|---|---|---|
max_request_body_bytes | integer | 16384 (16 KiB) | Per-request capture cap. Bodies larger than this are truncated to the prefix and request_body_truncated is set to true. Independent of proxy.max_request_body_bytes. |
rules | object[] | [] | Restrict capture to specific destinations. Uses the same format as allowlist.rules[]. |
- name: body_capture
config:
max_request_body_bytes: 16384
rules:
- host: "api.anthropic.com"
methods: ["POST"]
paths: ["/v1/messages"]When secrets runs with match_body: true, place body_capture before secrets so the audit log records the workload's proxy tokens rather than the real credentials swapped in.
annotate
Captures HTTP request header values into audit log annotations based on host, method, and path rules. This is useful for enriching audit logs with request-specific context like request IDs or authorization tokens. This transform never rejects requests.
| Field | Type | Default | Description |
|---|---|---|---|
annotations | object[] | [] | List of annotation groups. See below. |
annotate.annotations[]
| Field | Type | Description |
|---|---|---|
rules | object[] | Rules to match requests against. Uses the same format as allowlist.rules[]. |
headers | string[] | Header names to capture from matching requests. Values are written as header:<Name> entries in the transform trace. |
- name: annotate
config:
annotations:
- rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
headers: ["x-request-id", "authorization"]Example audit log annotation output:
{
"header:X-Request-Id": "req-abc123",
"header:Authorization": "Bearer sk-ant-..."
}grpc
Delegates request and response processing to an external gRPC server implementing the TransformService API. You can define multiple grpc transforms to pipeline through several servers.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Identifier for this transform, used in logs and error messages. |
target | string | required | gRPC server address (e.g., localhost:9500). |
send_request_body | boolean | false | When true, forward the full request body to the gRPC server. When false, only headers are sent. |
send_response_body | boolean | false | When true, forward the full response body to the gRPC server. When false, only headers are sent. |
rules | object[] | [] | Restrict which requests are forwarded to this server. Uses the same rule format as allowlist.rules[]. If empty, all requests are forwarded. |
tls | object | TLS configuration for the gRPC connection. See below. |
grpc.tls
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable TLS for the gRPC connection. When false, the connection uses plaintext. |
ca_cert | string | system default | Path to a custom CA certificate for server verification. |
cert | string | Path to a client certificate for mTLS. Must be set together with key. | |
key | string | Path to the client private key for mTLS. Must be set together with cert. |
judge
Calls a large language model to produce an allow or deny decision for outbound requests that match the instance's rules. Each judge entry is an independent instance with its own natural-language policy, LLM backend, semaphore, and circuit breaker. See the LLM Judge reference for a full overview, including writing policies, pipeline ordering, and envelope limits.
The judge can only reject. It never approves a request that the static allowlist would have denied.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Identifier for this instance, used in audit logs. Must be unique across judge instances. |
prompt | string | required | Natural-language policy sent to the LLM. Describes what is allowed and what is not. |
rules | object[] | required | Destinations this judge applies to. Uses the same format as allowlist.rules[]. At least one rule is required. |
provider | object | required | LLM backend configuration. See providers below. |
fallback | string | "deny" | Behavior when the LLM call fails, times out, returns malformed output, or the circuit breaker is open. One of deny (reject with HTTP 403) or skip (continue to the next transform). |
timeout | duration | "8s" | Maximum time for a single LLM call. |
max_concurrent | integer | 100 | Maximum concurrent in-flight LLM calls for this instance. Additional requests wait for a slot. |
circuit_breaker | object | Consecutive-failure breaker settings. See below. |
judge.circuit_breaker
| Field | Type | Default | Description |
|---|---|---|---|
consecutive_failures | integer | 5 | Number of consecutive failures that trips the breaker open. |
cooldown | duration | "10s" | How long the breaker stays open before admitting a single probe call. |
Judge Providers
The provider block selects and configures the LLM backend. The type field discriminates the shape of the block. Two providers are supported: anthropic and openai.
anthropic
Calls the Anthropic Messages API.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be anthropic. | |
model | string | required | Anthropic model ID (e.g., claude-haiku-4-5-20251001). |
api_key_env | string | required | Environment variable holding the Anthropic API key. |
base_url | string | https://api.anthropic.com | Override the API base URL. |
max_tokens | integer | 256 | Maximum tokens in the model response. |
provider:
type: "anthropic"
model: "claude-haiku-4-5-20251001"
api_key_env: "ANTHROPIC_API_KEY"
max_tokens: 256openai
Calls the OpenAI Chat Completions API.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be openai. | |
model | string | required | OpenAI model ID (e.g., gpt-5.4-nano). |
api_key_env | string | required | Environment variable holding the OpenAI API key. |
base_url | string | https://api.openai.com | Override the API base URL. Useful for Azure OpenAI or gateway deployments. |
max_tokens | integer | 256 | Maximum tokens in the model response. Sent as max_completion_tokens. |
provider:
type: "openai"
model: "gpt-5.4-nano"
api_key_env: "OPENAI_API_KEY"
max_tokens: 256Full Judge Example
- name: judge
config:
name: "github-write-guard"
fallback: "deny"
timeout: "8s"
max_concurrent: 100
circuit_breaker:
consecutive_failures: 5
cooldown: "10s"
rules:
- host: "api.github.com"
methods: ["POST", "PATCH", "DELETE", "PUT"]
provider:
type: "anthropic"
model: "claude-haiku-4-5-20251001"
api_key_env: "ANTHROPIC_API_KEY"
max_tokens: 256
prompt: |
This agent performs code review on the repository under review.
Allow writes to the comments and reviews endpoints of the specific
repository under review. Deny writes to user settings, organization
management, billing, or any repository the agent is not reviewing.Place the judge transform before the secrets transform so the LLM provider sees proxy tokens rather than real credentials. See pipeline ordering for details.
header_allowlist
Strips any request header not present in a configured allowlist before forwarding the request upstream. The allowlist uses a default-deny model: every header must match either a literal name or a regex pattern to pass through. Stripped header names are recorded in the trace annotation stripped_headers.
Place this transform after any transforms that inject headers (such as secrets) so injected headers survive the allowlist.
| Field | Type | Default | Description |
|---|---|---|---|
headers | string[] | [] | Allowed header names. Each entry is either a literal name (case-insensitive) or a /regex/ pattern compiled at config time as a case-insensitive regular expression matched against canonical header names. |
rules | object[] | [] | Restrict the allowlist to specific destinations. Uses the same format as allowlist.rules[]. When omitted, the allowlist applies to all requests. |
- name: header_allowlist
config:
headers:
- "Authorization"
- "Content-Type"
- "User-Agent"
- "Accept"
- "/^X-Trace-.*$/"
rules:
- host: "api.openai.com"mcp
Top-level block that enforces a default-deny tool allowlist on Streamable HTTP MCP servers. The interceptor runs after the transform pipeline, so allowlist still gates which hosts can be reached and secrets has already swapped proxy tokens before the interceptor evaluates the body.
See the MCP Interception reference for a full overview, including matchers, audit log behavior, and current limitations.
| Field | Type | Default | Description |
|---|---|---|---|
error | object | JSON-RPC error envelope returned for denied calls. See below. | |
servers | object[] | [] | List of MCP server policies. See below. |
mcp.error
| Field | Type | Default | Description |
|---|---|---|---|
code | integer | -32001 | JSON-RPC error code returned to the agent when a tools/call is denied. |
message | string | "blocked by iron-proxy policy" | Error message returned alongside code. |
mcp.servers[]
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Identifier for this server policy, used in audit logs. |
rules | object[] | required | Destinations this policy applies to. Uses the same format as allowlist.rules[]. |
tools | object[] | [] | Tools allowed on this server. Anything not listed is denied (default-deny). |
mcp.servers[].tools[]
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Tool name to allow. Matches the name field in the MCP tools/call request and the tools/list response. |
when | object[] | [] | Argument matchers. All matchers must pass for the call to be allowed. See below. |
mcp.servers[].tools[].when[]
| Field | Type | Default | Description |
|---|---|---|---|
path | string | required | Dotted path within params.arguments to evaluate (e.g., "owner", "repo"). |
equals | any | Exact match. Mutually exclusive with in and matches. | |
in | array | Match any of the listed values. Mutually exclusive with equals and matches. | |
matches | string | Regular expression match against the stringified value. Mutually exclusive with equals and in. |
mcp:
error:
code: -32001
message: "blocked by iron-proxy policy"
servers:
- name: github
rules:
- host: "mcp.github.com"
paths: ["/mcp", "/mcp/*"]
tools:
- name: "search_repositories"
- name: "create_issue"
when:
- path: "owner"
equals: "ironsh"
- path: "repo"
in: ["iron-proxy", "tunis-v2"]management
Configures an opt-in, bearer-authenticated HTTP management API. When set, iron-proxy listens on the configured address and exposes endpoints that operate on the running process. The API is disabled when the management block is omitted.
The first available endpoint is POST /v1/reload, which re-reads the YAML config from disk and atomically swaps in a freshly built transform pipeline with no restart and no dropped connections. Parse or build errors return HTTP 422 and leave the running pipeline untouched.
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | required | Address and port for the management API. Bind to a loopback address (e.g., 127.0.0.1:9092) unless the API is exposed via a separate network. |
api_key_env | string | IRON_MANAGEMENT_API_KEY | Environment variable holding the bearer token clients must present in the Authorization: Bearer ... header. |
management:
listen: "127.0.0.1:9092"
api_key_env: "IRON_MANAGEMENT_API_KEY"Reload a running proxy:
curl -X POST http://127.0.0.1:9092/v1/reload \
-H "Authorization: Bearer $IRON_MANAGEMENT_API_KEY"metrics
Configures the OpenTelemetry/Prometheus metrics endpoint.
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":9090" | Address and port for the metrics endpoint. |
log
Configures logging output.
| Field | Type | Default | Description |
|---|---|---|---|
level | string | "info" | Log verbosity. One of debug, info, warn, or error. |
OpenTelemetry Environment Variables
OTEL log export is configured through environment variables, not the YAML config file. Set OTEL_EXPORTER_OTLP_ENDPOINT to enable it. See the OTEL export guide for usage examples.
| Variable | Type | Default | Description |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | string | (disabled) | OTLP collector URL (e.g., https://otel-collector.example.com:4318). Export is disabled when unset. |
OTEL_EXPORTER_OTLP_PROTOCOL | string | http/protobuf | Transport protocol: http/protobuf or grpc. |
OTEL_EXPORTER_OTLP_HEADERS | string | (none) | Comma-separated key=value pairs sent as headers on every export request. Typically used for authentication. |
OTEL_SERVICE_NAME | string | iron-proxy | Service name attached to all log records. |
OTEL_RESOURCE_ATTRIBUTES | string | (none) | Comma-separated key=value resource attributes added to all log records (e.g., deployment.environment=staging). |