OpenWork Support Report: 403 Forbidden on OpenWork Models inference from the desktop app engine
Date: 2026-08-02
From: Brad Strawbridge
App: OpenWork desktop, version 0.18.12 (macOS, arm64)
Impact: Every OpenWork Models provider (base "OpenWork Models" and "OpenWork Models - key 2") returns HTTP 403 from the app's built-in engine. The app's own chat client is blocked by the WAF in front of the inference endpoint. No model requests work through this path.
Status: ROOT CAUSE FOUND. The WAF blocks the engine's request body because it contains a string that matches a command-injection signature rule: a backtick-wrapped curl/wget command followed by an http(s):// URL. The engine's system prompt, which opencode builds from the operator's ~/.claude/CLAUDE.md (AGENTS.md), contains exactly this pattern. This is a false positive: the pattern is documentation inside the system prompt, not an attack payload.
Summary
The desktop app's bundled engine (the opencode-aarch64-apple-darwin sidecar, which embeds Bun v1.3.14) cannot reach https://inference.openworklabs.com/api/v1. Every chat attempt returns:
Forbidden: request was blocked by a gateway or proxy.
Status: 403
WAF block pages show the Render/Cloudflare stack (Powered by render.com, server: cloudflare, __cf_bm cookie for the openrouter.ai upstream, cf-ray headers). Latest Request ID: a24a2d6fbb7e44f6 (earlier: a249f0db7cf6b827). Client IP shown on the block page: 76.205.202.11.
Why this is not an account, key, or IP problem
From the same machine, same IP (76.205.202.11), same inference API key, same request body (exact AI SDK streaming shape, stream: true, stream_options.include_usage: true), same headers (User-Agent opencode/1.17.11, HTTP-Referer, X-Title, and the openai-node stainless header set), same Bun version:
| Client |
Result |
curl (HTTP/1.1 and HTTP/2) |
HTTP 200 |
| System Bun v1.3.14 (matches the version embedded in the engine), 26 of 26 requests |
HTTP 200 |
| Engine embedded in the OpenWork app (PID 69958, run 142e6d01) |
HTTP 403, always |
Additional controls already ruled out:
- Credential: key-2 had a stale key at one point, but it was corrected to the same key that curl and Bun use successfully, and the app was fully restarted. The base "OpenWork Models" provider had the correct key the entire time and still gets 403.
- DNS resolution order: the engine runs with
BUN_CONFIG_DNS_RESULT_ORDER=verbatim; testing stock Bun with the same env var changes nothing (200).
- TLS trust store: the engine is pointed at
NODE_EXTRA_CA_CERTS=/Users/bradstrawbridge/Library/Application Support/com.differentai.openwork/system-ca-bundle.pem, which exists and is a valid PEM bundle.
- No proxy variables are set for the engine.
Root cause (proven by controlled reproduction)
I captured the engine's exact raw request (TLS handshake + full HTTP request and body) with local MITM listeners, then replayed every layer separately with stock Bun v1.3.14 from the same machine:
| Variable tested |
Result |
| TLS ClientHello (JA3/JA4) of the engine binary vs stock Bun |
Identical. JA3 ec8303300b5749d0ba5eecda6d04e3c4, JA4 t13d1714h1_5b57614c22b0_6ad430c8ee9d, same 17-cipher list, same 14-extension order, same supported groups. TLS fingerprint ruled out. |
Engine's exact header set (full UA opencode/1.17.11 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14, Accept: */*, Connection: keep-alive, x-session-affinity, x-session-id) |
6/6 HTTP 200 with stock Bun |
Engine's exact 127 KB request body (system prompt + user message, max_tokens: 32000, stream: true) |
3/3 HTTP 403 with stock Bun |
| Same body with the user message only |
HTTP 200 |
| Same body with system prompt truncated to 60 KB |
HTTP 403 |
| Same body with system prompt sliced to 10 KB chunks |
Only bytes [20000,30000) blocked |
| Bisection of that slice |
Exact 39-byte trigger at [27187,27226): = `curl -sf http://127.0.0.1:8084/healt |
The trigger string, verbatim, is the Zengram health check documented in the operator's AGENTS.md (which opencode loads as the system prompt):
- Zengram healthy = `curl -sf http://127.0.0.1:8084/health` returns 200 with `"status":"ok"`
Signature rule behavior confirmed with minimal payloads:
`curl http://127.0.0.1/x` → 403 (any IP literal, including public 8.8.8.8)
`curl http://example.com/x` → 403 (public domain too)
`wget http://example.com/x` → 403
`curl http://localhost/x` → 200 (localhost hostname is exempted, IP literals are not)
curl http://127.0.0.1/x without backticks → 200
`curl /tmp/somefile` (no URL) → 200
`echo hello`, `ls -la /etc` → 200
Conclusion: the WAF in front of inference.openworklabs.com runs a managed rule that blocks request bodies containing backtick-wrapped curl/wget with an http(s):// URL. This is a standard command-injection/exfiltration signature. Any legitimate agent system prompt that documents a curl command (extremely common) will be blocked. The engine was not flagged for its TLS fingerprint or headers; its system prompt simply contains the offending string.
Engine log evidence
~/.local/share/opencode/log/opencode.log, after the credential fix and full app restart:
2026-08-02T03:19:24.707Z level=ERROR run=142e6d01 message="stream error"
providerID=lpr_01kyv4v3xqf20stc0z1hgje5xf modelID=moonshotai/kimi-k3
session.id=ses_04f15d75cffe1bSkkfa0YR1Hq7
error.error="AI_APICallError: Forbidden"
stack="AI_APICallError: Forbidden at <anonymous> (/$bunfs/root/chunk-y3ky3p4y.js:12:14045)"
Same failure earlier with the base provider at 02:59:21 (correct key, never rotated).
How to reproduce
- Install OpenWork 0.18.12 on macOS arm64.
- Sign in with an OpenWork Models entitlement (base provider, or key-2 import with 9 models).
- Pick any key-2 model (e.g.
moonshotai/kimi-k3) and send a chat message.
- Expected: model streams a reply. Actual:
403 Forbidden: request was blocked by a gateway or proxy.
- From the same machine, run:
curl https://inference.openworklabs.com/api/v1/chat/completions -H "Authorization: Bearer <ow_inf_... key>" -H "Content-Type: application/json" -d '{"model":"moonshotai/kimi-k3","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'
which returns HTTP 200.
- Minimal WAF trigger (no app involved, plain stock Bun):
bun -e 'fetch("https://inference.openworklabs.com/api/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json","Authorization":"Bearer <ow_inf_... key>"},body:JSON.stringify({model:"moonshotai/kimi-k3",messages:[{role:"system",content:"backtick curl http://127.0.0.1/x backtick"}],max_tokens:8})}).then(r=>console.log(r.status))'
returns 403. Replace the system content with `curl http://localhost/x` and it returns 200. The rule is content-based, not client-based.
Requested action
The WAF managed rule is producing false positives on legitimate LLM API traffic: it inspects request bodies for command-injection signatures, but agent system prompts routinely document curl/wget commands as part of their operating instructions. Please add an allowlist or rule override for the /api/v1/chat/completions request body inspection on inference.openworklabs.com (or scope the signature rule to header/URL inspection only). Until that ships, the desktop app's own OpenWork Models product stays broken for any agent whose system prompt documents a curl command, which includes the standard opencode runtime bootstrap.
Happy to provide anything else needed (full logs, binary hashes, packet captures, the full 127 KB captured request and body).
OpenWork Support Report: 403 Forbidden on OpenWork Models inference from the desktop app engine
Date: 2026-08-02
From: Brad Strawbridge
App: OpenWork desktop, version 0.18.12 (macOS, arm64)
Impact: Every OpenWork Models provider (base "OpenWork Models" and "OpenWork Models - key 2") returns HTTP 403 from the app's built-in engine. The app's own chat client is blocked by the WAF in front of the inference endpoint. No model requests work through this path.
Status: ROOT CAUSE FOUND. The WAF blocks the engine's request body because it contains a string that matches a command-injection signature rule: a backtick-wrapped
curl/wgetcommand followed by anhttp(s)://URL. The engine's system prompt, which opencode builds from the operator's~/.claude/CLAUDE.md(AGENTS.md), contains exactly this pattern. This is a false positive: the pattern is documentation inside the system prompt, not an attack payload.Summary
The desktop app's bundled engine (the
opencode-aarch64-apple-darwinsidecar, which embeds Bun v1.3.14) cannot reachhttps://inference.openworklabs.com/api/v1. Every chat attempt returns:WAF block pages show the Render/Cloudflare stack (
Powered by render.com,server: cloudflare,__cf_bmcookie for theopenrouter.aiupstream, cf-ray headers). Latest Request ID:a24a2d6fbb7e44f6(earlier:a249f0db7cf6b827). Client IP shown on the block page:76.205.202.11.Why this is not an account, key, or IP problem
From the same machine, same IP (
76.205.202.11), same inference API key, same request body (exact AI SDK streaming shape,stream: true,stream_options.include_usage: true), same headers (User-Agentopencode/1.17.11, HTTP-Referer, X-Title, and the openai-node stainless header set), same Bun version:curl(HTTP/1.1 and HTTP/2)Additional controls already ruled out:
BUN_CONFIG_DNS_RESULT_ORDER=verbatim; testing stock Bun with the same env var changes nothing (200).NODE_EXTRA_CA_CERTS=/Users/bradstrawbridge/Library/Application Support/com.differentai.openwork/system-ca-bundle.pem, which exists and is a valid PEM bundle.Root cause (proven by controlled reproduction)
I captured the engine's exact raw request (TLS handshake + full HTTP request and body) with local MITM listeners, then replayed every layer separately with stock Bun v1.3.14 from the same machine:
ec8303300b5749d0ba5eecda6d04e3c4, JA4t13d1714h1_5b57614c22b0_6ad430c8ee9d, same 17-cipher list, same 14-extension order, same supported groups. TLS fingerprint ruled out.opencode/1.17.11 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14,Accept: */*,Connection: keep-alive,x-session-affinity,x-session-id)max_tokens: 32000,stream: true)= `curl -sf http://127.0.0.1:8084/healtThe trigger string, verbatim, is the Zengram health check documented in the operator's AGENTS.md (which opencode loads as the system prompt):
Signature rule behavior confirmed with minimal payloads:
`curl http://127.0.0.1/x`→ 403 (any IP literal, including public8.8.8.8)`curl http://example.com/x`→ 403 (public domain too)`wget http://example.com/x`→ 403`curl http://localhost/x`→ 200 (localhost hostname is exempted, IP literals are not)curl http://127.0.0.1/xwithout backticks → 200`curl /tmp/somefile`(no URL) → 200`echo hello`,`ls -la /etc`→ 200Conclusion: the WAF in front of
inference.openworklabs.comruns a managed rule that blocks request bodies containing backtick-wrappedcurl/wgetwith anhttp(s)://URL. This is a standard command-injection/exfiltration signature. Any legitimate agent system prompt that documents a curl command (extremely common) will be blocked. The engine was not flagged for its TLS fingerprint or headers; its system prompt simply contains the offending string.Engine log evidence
~/.local/share/opencode/log/opencode.log, after the credential fix and full app restart:Same failure earlier with the base provider at 02:59:21 (correct key, never rotated).
How to reproduce
moonshotai/kimi-k3) and send a chat message.403 Forbidden: request was blocked by a gateway or proxy.curl https://inference.openworklabs.com/api/v1/chat/completions -H "Authorization: Bearer <ow_inf_... key>" -H "Content-Type: application/json" -d '{"model":"moonshotai/kimi-k3","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'which returns HTTP 200.
bun -e 'fetch("https://inference.openworklabs.com/api/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json","Authorization":"Bearer <ow_inf_... key>"},body:JSON.stringify({model:"moonshotai/kimi-k3",messages:[{role:"system",content:"backtick curl http://127.0.0.1/x backtick"}],max_tokens:8})}).then(r=>console.log(r.status))'returns 403. Replace the system content with
`curl http://localhost/x`and it returns 200. The rule is content-based, not client-based.Requested action
The WAF managed rule is producing false positives on legitimate LLM API traffic: it inspects request bodies for command-injection signatures, but agent system prompts routinely document curl/wget commands as part of their operating instructions. Please add an allowlist or rule override for the
/api/v1/chat/completionsrequest body inspection oninference.openworklabs.com(or scope the signature rule to header/URL inspection only). Until that ships, the desktop app's own OpenWork Models product stays broken for any agent whose system prompt documents a curl command, which includes the standard opencode runtime bootstrap.Happy to provide anything else needed (full logs, binary hashes, packet captures, the full 127 KB captured request and body).