Skip to content

feat(api-proxy): Anthropic prompt-cache injection, TTL upgrade, tool drop, ANSI strip, custom transform hook#2360

Merged
lpcox merged 9 commits intomainfrom
copilot/optimize-api-proxy-efficiency
May 1, 2026
Merged

feat(api-proxy): Anthropic prompt-cache injection, TTL upgrade, tool drop, ANSI strip, custom transform hook#2360
lpcox merged 9 commits intomainfrom
copilot/optimize-api-proxy-efficiency

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 1, 2026

AWF's api-proxy sits in the exact request path where pino operates but lacked its caching optimisations. On a typical 30-turn Claude Code session, uncached input tokens dominate cost (~$4–20/session avoidable). This PR adds five opt-in transforms on POST /v1/messages via the existing bodyTransform chain.

New module: containers/api-proxy/anthropic-transforms.js

All transforms are pure, idempotent, zero-new-dependencies functions composed into a single Buffer→Buffer pipeline.

1. Auto-inject prompt-cache breakpoints (AWF_ANTHROPIC_AUTO_CACHE=1)

Injects cache_control: {type:"ephemeral", ttl:"1h"} at up to 4 slots (Anthropic's ceiling):

Slot Target TTL ~Tokens saved/turn
1 Last tools entry 1h ~24 k
2 Last system block 1h ~8 k
3 Last block of messages[0] 1h ~5 k
4 Rolling tail (last block, last message) tailTtl ~15 k

Also adds anthropic-beta: extended-cache-ttl-2025-04-11 to upstream requests, merging with any existing client beta flags (handles both string and string-array header values safely).

2. Upgrade existing ephemeral TTLs (implied by AWF_ANTHROPIC_AUTO_CACHE)

Rewrites client-injected {type:"ephemeral"} breakpoints that lack a ttl to 1h. The rolling tail stays at AWF_ANTHROPIC_CACHE_TAIL_TTL (default 5m) to avoid the 2× write surcharge on a moving breakpoint.

3. Drop unused tools (AWF_ANTHROPIC_DROP_TOOLS=NotebookEdit,CronCreate,…)

Strips named tools from tools[] and scrubs references from system text blocks. The word-boundary regex is compiled once at startup, not per-request.

4. Strip ANSI from tool_result blocks (AWF_ANTHROPIC_STRIP_ANSI=1)

Removes SGR sequences (\x1B[…m) from terminal output in tool results — halves token count in colour-heavy outputs and eliminates cache misses caused by colour-code variation across turns.

5. Custom transform hook (AWF_ANTHROPIC_TRANSFORM_FILE=/path/to/hook.js)

// hook.js — loaded once at startup; called after built-in transforms
module.exports = function transform(body) {
  return { ...body, system: [...body.system, { type: 'text', text: 'Extra context.' }] };
};

Security note: AWF_ANTHROPIC_TRANSFORM_FILE is not forwarded from the host environment into the api-proxy container. The api-proxy holds live API credentials; loading arbitrary host-side JS files into it would create an arbitrary code execution risk. To use a custom transform, bake your hook.js into a custom container image and set AWF_ANTHROPIC_TRANSFORM_FILE in that image's Dockerfile/entrypoint — never point it at a host path forwarded via env var.

Integration (server.js)

  • New composeBodyTransforms(first, second) utility chains the existing model-alias transform with the Anthropic optimisations transform; model alias runs first.
  • All env vars are read and logged at startup; the composed transform and tool-drop regex are built once at module init.
  • require('./anthropic-transforms') is wrapped in a try/catch so the container degrades gracefully if the module is absent.

Dockerfile

containers/api-proxy/Dockerfile updated to include anthropic-transforms.js in the explicit COPY list so the module is present in the container image.

Env var forwarding (src/docker-manager.ts)

Four AWF_ANTHROPIC_* env vars are forwarded from the host environment to the api-proxy container: AWF_ANTHROPIC_AUTO_CACHE, AWF_ANTHROPIC_CACHE_TAIL_TTL, AWF_ANTHROPIC_DROP_TOOLS, and AWF_ANTHROPIC_STRIP_ANSI. AWF_ANTHROPIC_TRANSFORM_FILE is intentionally not forwarded (see security note above).

Tests

72 new tests: 64 in anthropic-transforms.test.js (per-function, idempotency, mutation safety, edge cases), 7 for composeBodyTransforms, 6 for env-var forwarding in docker-manager.test.ts.

Copilot AI added 2 commits May 1, 2026 15:58
- Add anthropic-transforms.js: injectCacheBreakpoints, upgradeEphemeralTtl,
  applyAnsiStrip, applyToolDrop, loadCustomTransform, makeAnthropicTransform
- Add composeBodyTransforms utility to chain body-transform functions
- Inject anthropic-beta: extended-cache-ttl-2025-04-11 when AWF_ANTHROPIC_AUTO_CACHE=1
- Forward AWF_ANTHROPIC_* env vars to api-proxy container in docker-manager.ts
- 57 new transform tests + 7 compose tests + 6 docker-manager forwarding tests
- Extract buildToolScrubPattern helper so the regex is precompiled once
  in makeAnthropicTransform, not on every request
- Accept optional pre-compiled scrubPattern in applyToolDrop to avoid
  per-request regex construction overhead
- Optimize beta-header includes check: parse split result once before
  calling includes
- Add test: stripAnsi preserves non-SGR sequences when mixed with SGR
- Add tests: buildToolScrubPattern reuse safety + applyToolDrop pre-compiled path

Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/dc1e196f-357e-4c3d-8099-e1ad370ab09c
Copilot AI changed the title [WIP] Optimize api-proxy efficiency with prompt-cache injection and TTL upgrades feat(api-proxy): Anthropic prompt-cache injection, TTL upgrade, tool drop, ANSI strip, custom transform hook May 1, 2026
Copilot finished work on behalf of lpcox May 1, 2026 16:04
Copilot AI requested a review from lpcox May 1, 2026 16:04
@lpcox lpcox marked this pull request as ready for review May 1, 2026 16:54
@lpcox lpcox requested a review from Mossaka as a code owner May 1, 2026 16:54
Copilot AI review requested due to automatic review settings May 1, 2026 16:54
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 85.90% 85.98% 📈 +0.08%
Statements 85.88% 85.96% 📈 +0.08%
Functions 88.00% 88.00% ➡️ +0.00%
Branches 80.00% 80.10% 📈 +0.10%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 87.4% → 87.7% (+0.29%) 87.0% → 87.3% (+0.27%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Comment thread containers/api-proxy/server.test.js Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in Anthropic-specific request-body transforms to the api-proxy to reduce Claude session costs via prompt-cache breakpoint injection, TTL upgrading, tool dropping, ANSI stripping, and an optional custom transform hook, integrated into the existing bodyTransform chain.

Changes:

  • Introduces anthropic-transforms.js implementing a composed Buffer→Buffer(/null) transform pipeline plus comprehensive unit tests.
  • Integrates Anthropic optimizations into containers/api-proxy/server.js via composeBodyTransforms and conditional anthropic-beta header merging.
  • Forwards Anthropic-related env vars into the api-proxy container via generateDockerCompose and adds forwarding tests.
Show a summary per file
File Description
src/docker-manager.ts Forwards selected AWF_ANTHROPIC_* env vars into the api-proxy container environment.
src/docker-manager.test.ts Adds tests asserting Anthropic env var forwarding behavior.
containers/api-proxy/server.js Wires in Anthropic transform pipeline, adds transform composition helper, and conditionally merges anthropic-beta.
containers/api-proxy/server.test.js Adds unit tests for composeBodyTransforms.
containers/api-proxy/anthropic-transforms.js New module implementing Anthropic request-body transforms (cache breakpoints, TTL upgrades, tool drop, ANSI strip, custom hook).
containers/api-proxy/anthropic-transforms.test.js New test suite validating transform behavior, idempotency, and custom hook loading.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 3

Comment thread containers/api-proxy/server.js Outdated
Comment thread containers/api-proxy/server.js Outdated
Comment thread src/docker-manager.ts
lpcox and others added 4 commits May 1, 2026 10:01
…ort, function or class'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 1, 2026

@copilot merge main and resolve conflicts

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Security Review: PR #2360

Reviewed containers/api-proxy/anthropic-transforms.js, containers/api-proxy/server.js, and src/docker-manager.ts.


Finding 1 — Critical: Arbitrary code execution in the API-credential-holding sidecar

File: containers/api-proxy/anthropic-transforms.js:344–365 (loadCustomTransform) and containers/api-proxy/server.js:549–551

// anthropic-transforms.js:347-348
const absolutePath = path.resolve(filePath);
const mod = require(absolutePath);

AWF_ANTHROPIC_TRANSFORM_FILE is forwarded from the host environment (docker-manager.ts:1823) into the api-proxy container, then passed verbatim to Node.js require(). This executes arbitrary JavaScript inside the api-proxy sidecar — the one container in AWF's architecture that holds live API credentials (ANTHROPIC_API_KEY, OPENAI_API_KEY, COPILOT_GITHUB_TOKEN, etc.).

The api-proxy's log volume is mounted read-write on the host (/var/log/api-proxy:rw, docker-manager.ts:1767-1769). If an attacker can write a .js file to that host path and set AWF_ANTHROPIC_TRANSFORM_FILE=/var/log/api-proxy/x.js before container start, they get code execution with full access to all API keys in the container environment.

Suggested action: Add a strict allowlist of permitted transform file paths (e.g., only paths within the container image's /app directory), or restrict the feature to container-image-baked transforms only (no env-var-specified paths accepted from the host).


Finding 2 — Moderate: Feature design creates pressure to add insecure host bind mounts

File: src/docker-manager.ts:1766-1769 (api-proxy volumes), docker-manager.ts:1823

The PR description and code comment in server.js:531 show the intended usage as:

AWF_ANTHROPIC_TRANSFORM_FILE=/path/to/hook.js   # file inside container

However, no bind mount is added for this path in generateDockerCompose(). The env var is forwarded but the referenced host file won't exist inside the container unless it was baked into the image. Users who discover this feature doesn't work with host paths are likely to add ad-hoc bind mounts of host directories into the api-proxy container (a container that holds real API keys) to make it functional — significantly widening the sidecar's attack surface.

Suggested action: Either (a) document explicitly that this only works with files pre-baked in a custom container image and never forward the env var from the host (since it would resolve against paths inside the container image anyway), or (b) if host files are intended, add a narrow, validated bind mount and path-prefix check before require().

Generated by Security Guard for issue #2360 · ● 166.3K ·

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot look into this failed ci check https://github.com/github/gh-aw-firewall/actions/runs/25224279521/job/73963967905?pr=2360

This is a flaky Gemini smoke test failure unrelated to our changes. The "Execute Gemini CLI" step ran for ~2 minutes (17:13:01–17:14:59) but the Gemini agent produced no safe outputs — no add_comment call was recorded. This is an AI agent nondeterminism/timeout issue, not a code defect.

Evidence that this is pre-existing flakiness:

  • Run 143 on main itself also failed with the same "No safe outputs were invoked" error
  • Multiple other unrelated branches show the same intermittent pattern (some passing on retry)
  • Our changes only touch the Anthropic api-proxy path (port 10001) — the Gemini proxy (port 10003) is completely unaffected

No code fix needed; this failure is not caused by changes in this PR.

Copilot finished work on behalf of lpcox May 1, 2026 17:20
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 1, 2026

@copilot merge main and address this review feedback #2360 (comment)

…proxy container

The api-proxy container holds live API credentials. Forwarding a host-provided
file path to require() inside that container creates an arbitrary code execution
risk (security review Finding 1 & 2). Remove the env var forwarding; users who
need a custom transform must bake their hook.js into a custom container image.

Add explanatory comments in docker-manager.ts and server.js.

Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/7591d987-6d79-4670-8429-1e2c42f7a496

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot merge main and address this review feedback #2360 (comment)

Main was already merged (it's an ancestor of HEAD). Addressed the security review in feb1502:

  • Removed AWF_ANTHROPIC_TRANSFORM_FILE forwarding from docker-manager.ts (Findings 1 & 2). The api-proxy container holds live API credentials; forwarding a host-provided file path to require() inside it creates an arbitrary code execution risk — especially since the log volume is mounted read-write on the host.
  • Added an explanatory comment noting that users who need a custom transform must bake their hook.js into a custom container image and set the env var there directly.
  • Updated the server.js env var documentation comment to document this restriction.

The transform hook feature (AWF_ANTHROPIC_TRANSFORM_FILE) still works when the file is pre-baked in a custom container image; it just can no longer be loaded from a host path via env var forwarding.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🔥 Smoke Test: Copilot BYOK — PASS

Test Result
GitHub MCP (list PRs)
GitHub.com connectivity
File write/read (/tmp/gh-aw/agent/smoke-test-copilot-byok-25224916244.txt)
BYOK inference (agent → api-proxy → api.githubcopilot.com)

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com.

PR by @Copilot, assignees: @lpcox @Copilot. Overall: PASS

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Smoke test results:

Status: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Smoke Test

feat(api-proxy): add Anthropic prompt-cache optimizations (AWF_ANTHROPIC_AUTO_CACHE)
fix(api-proxy): set GOOGLE_GEMINI_BASE_URL to route Gemini CLI through sidecar
GitHub MCP: ❌ unavailable in session
safeinputs-gh: ❌ unavailable in session
Playwright: ✅
Tavily: ❌ unavailable in session
File+cat: ✅
Discussion comment: ✅
Build: ✅
Overall: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.1 v20.20.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Result: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Smoke Test Results

  • Redis PING: ❌ (timeout — host.docker.internal:6379 unreachable)
  • PostgreSQL pg_isready: ❌ (host.docker.internal:5432 — no response)
  • PostgreSQL SELECT 1: ❌ (skipped — host unreachable)

Overall: FAILhost.docker.internal is not reachable from this runner environment. Service containers may not be configured or the hostname is not available.

🔌 Service connectivity validated by Smoke Services

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Security Review: PR #2360

Reviewed by the security guard workflow.

Finding: Unrestricted require() in credential-holding container (loadCustomTransform)

File: containers/api-proxy/anthropic-transforms.js:344–365
Severity: Moderate (guarded, but no defense-in-depth)

function loadCustomTransform(filePath) {
  if (!filePath) return null;
  try {
    const absolutePath = path.resolve(filePath);  // no path restriction
    const mod = require(absolutePath);             // executes arbitrary code
    ...
  }
}

require(path.resolve(filePath)) will execute any readable JavaScript file in the api-proxy container — a container that holds live API credentials. The entire security guarantee is the one comment at src/docker-manager.ts:1823:

// NOTE: AWF_ANTHROPIC_TRANSFORM_FILE is intentionally NOT forwarded from the host.

Threat scenarios if that protection is circumvented:

  • Custom base image with ENV AWF_ANTHROPIC_TRANSFORM_FILE=... baked in pointing to a malicious file
  • docker-compose.override.yml setting the env var (e.g., in a CI environment where a developer tests locally)
  • A future accidental forwarding (copy-paste of env var forwarding block)

Suggested mitigation (defense-in-depth inside the function):

function loadCustomTransform(filePath) {
  if (!filePath) return null;
  const absolutePath = path.resolve(filePath);
  // Restrict to paths within the container image; reject anything
  // that could point at host-mounted paths.
  const SAFE_PREFIX = path.resolve('/app');
  if (!absolutePath.startsWith(SAFE_PREFIX + path.sep) && absolutePath !== SAFE_PREFIX) {
    console.error(`[anthropic-transforms] AWF_ANTHROPIC_TRANSFORM_FILE "\$\{filePath}" resolves outside /app — refused`);
    return null;
  }
  ...
}

This turns a single-point-of-failure protection (one line in docker-manager.ts) into a two-layer defence.


All other security checks passed: no iptables/Squid ACL regressions, no new open ports, no capability additions, no seccomp relaxations, and AWF_ANTHROPIC_TRANSFORM_FILE is correctly blocked from host-env forwarding.

Generated by Security Guard for issue #2360 · ● 176.4K ·

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx All passed ✅ PASS
Node.js execa All passed ✅ PASS
Node.js p-limit All passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #2360 · ● 464.9K ·

@lpcox lpcox merged commit 91b0094 into main May 1, 2026
64 of 69 checks passed
@lpcox lpcox deleted the copilot/optimize-api-proxy-efficiency branch May 1, 2026 18:20
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🧪 Smoke Test Results

Test Status
GitHub MCP connectivity ✅ PASS
GitHub.com HTTP connectivity ⚠️ UNVERIFIED (template vars unresolved)
File write/read ⚠️ UNVERIFIED (template vars unresolved)

PR: feat(api-proxy): Anthropic prompt-cache injection, TTL upgrade, tool drop, ANSI strip, custom transform hook
Author: @Copilot | Assignees: @lpcox, @Copilot

Overall: PARTIAL — MCP ✅, pre-step outputs not injected into prompt.

📰 BREAKING: Report filed by Smoke Copilot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api-proxy efficiency: prompt-cache injection, TTL upgrading, tool dropping, ANSI stripping (inspired by alxsuv/pino)

4 participants