Changelog

View on GitHub →

New: Configurable Upstream Response-Header Timeout

The HTTP transport's response-header timeout was previously hard-coded to 30 seconds, which tripped on legitimately slow upstreams (e.g. large LLM context-compaction calls) and returned 502 even though the upstream was healthy. The timeout is now configurable from the policy YAML, defaulting to the existing 30s so current deployments behave identically.

proxy:
  upstream_response_header_timeout: "5m"   # optional; default "30s"

Invalid or non-positive durations are rejected at Validate. Thanks to @tsenart for the contribution.

New: Wildcard Host in Allowlist Matcher

hostmatch.MatchGlob now treats a bare "*" as a match for any host, letting operators write a single catch-all entry in the allowlist instead of enumerating hosts.

allowlist:
  - host: "*"

Other

  • Pinned all GitHub Actions to commit SHAs (#75).
  • Bumped various dependencies.
View on GitHub →

Fixes

  • Set the default managed ingest endpoint to https://ingest.iron.sh/v1/logs so OTel logs are routed to the correct path (#74).
  • Bumped various dependencies
View on GitHub →

New: LLM Judge Transform

A new judge transform calls an LLM to produce an allow/deny decision for requests that match its URL rules. Each entry under transforms: is an independent judge instance with its own natural-language policy and LLM backend. Operators can deploy zero, one, or many judges scoped to different rules.

Anthropic and OpenAI are supported in v1. The judge can only reject: it never approves a request the static allowlist would have denied, and static deny always wins. On LLM error, timeout, open circuit breaker, or malformed model output, the configured fallback applies (deny by default).

- 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: |
      Natural-language policy describing what is allowed for requests that
      match the rules above.

Every matched request adds structured fields to the audit trace: judge.instance, judge.decision, judge.reason, judge.duration_ms, judge.input_tokens, judge.output_tokens, and judge.fallback_applied or judge.circuit_breaker_tripped when those fire.

For more information, see the LLM Judge reference.

Thanks to Brex for their CrabTrap project, which inspired this design.

View on GitHub →

New: version Subcommand

The proxy now ships a version subcommand that prints the build version populated via -ldflags at build time. --version and -v are also accepted for convention.

iron-proxy version

Fix: Transform Annotations as Nested OTEL Values

Transform annotations were previously JSON-encoded into a string before being attached to OTEL audit log records, which caused them to render as stringified JSON rather than structured data. Annotations are now emitted as a proper log.MapValue/log.SliceValue tree, matching the slog-based audit logger and the OTEL log data model's native support for nested AnyValue.

View on GitHub →

New: SNI-Only TLS Mode

A new tls.mode config option accepts "mitm" (default) or "sni-only". In sni-only mode the HTTPS listener peeks the TLS ClientHello SNI and TCP-passthroughs to the upstream without terminating TLS, so clients don't 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"