Repository navigation
🚀 What's New
v0.10.1 is a security and hardening release. It closes the credential-redaction and body-size gaps found in the last two release reviews: request and response bodies, streamed (SSE) responses and forward_webhook bodies are now redacted, response bodies and batch items respect max_captured_body_bytes, unsigned OpenAI webhooks are rejected in development and test when a secret is set, and Amazon Bedrock FIPS, dual-stack and China endpoints are captured. It also contains one breaking rename (public_content_service, below), so please read the upgrade notes.
✨ New: Amazon Bedrock FIPS, dual-stack and China endpoints
The default intercept list held only bedrock-runtime.*.amazonaws.com. Calls to bedrock-runtime-fips.<region>.amazonaws.com (the usual choice for GovCloud), the dual-stack IPv6 hosts bedrock-runtime.<region>.api.aws and bedrock-runtime-fips.<region>.api.aws, and the China partition bedrock-runtime.<region>.amazonaws.com.cn were silently not captured. They are now. (A VPC interface endpoint host still needs its own entry in intercept_addresses.)
🔒 Security Hardening
- Credentials in request and response bodies are redacted. String values under
authorization,authorization_token,access_token,refresh_token,id_token,api_key,client_secret,private_key,secret_key,password,x-api-key,secret,aws_secret_access_key,session_tokenandbearer_token(any depth, exact name) become[REDACTED], as do MCP toolheadersand Realtime ephemeral keys.response_bodyis now redacted too, not justrequest_body. String values underurl,server_url,image_url,file_urlandendpointthat are a single absolute URL go through the URL sanitizer. Baretokenis left alone so logprobs keep working. A secret embedded in a URL path cannot be detected and is sent as is. See Body Redaction. - Streamed (SSE) responses and
forward_webhookbodies are redacted. The JSON object or array on eachdata:line, JSON text passed as a string, and therequest_body/response_bodygiven toCoolhand.logger_service.forward_webhookare parsed, redacted and re-serialized.[DONE], event and comment lines and plain text are untouched; a payload that cannot be redacted is replaced by aredaction_failedmarker rather than logged unchecked. - Unsigned OpenAI webhooks are rejected in
developmentandtestwhen a secret is set. Previously a request missing the signature headers passed in those environments, so an app running withRAILS_ENV=developmenton a reachable host accepted forgedbatch.completedevents. A nil payload is now rejected as well. With no secret configured, development and test still skip verification with a warning. WebhookValidatorreplay protection: webhook ids are remembered for twice the tolerance window (closing a replay gap at the edge of the window), and the claim is released when yourprocess_eventraises, so OpenAI's retry of the samewebhook-idis processed instead of rejected.OpenAi::BatchResultProcessorvalidates ids before use. The batch id and input/output file ids are format-checked before any OpenAI API call made with your key, and a batch with missing timestamps is skipped instead of logged with epoch-0 times. Batch logs now carrysource_api: "openai", themodel, and an absolutehttps://api.openai.com/...URL.forward_webhookno longer forwards the whole Rack env. Passingrequest.headersshipped session and request data (rack.session,action_dispatch.*) and CGI variables such asQUERY_STRING. When the input looks like a whole Rack env, onlyHTTP_*,CONTENT_TYPEandCONTENT_LENGTHare forwarded, so process env vars such asDATABASE_URLare dropped too. A caller-suppliedurl:is sanitized.- URL and header redaction gaps closed. The query params
code,sas,pwd,passwd,pass,sid,otp,jwt,bearer,session,cookie,passphrase,ticketandassertionare redacted;;name=valuematrix params and bare query segments are handled; URL fragments (#access_token=...) are dropped. Rate-limit headers (x-ratelimit-remaining-tokens, ...) are no longer over-redacted. Invalid UTF-8 in a URL no longer drops the log. - Path matching is normalized. Repeated
/,./..segments and unreserved percent-escapes are normalized beforeexclude_api_patternsand path-anchoredintercept_addressesmatching, so/openai//filesor/openai/%66ilescan no longer slip past an exclusion. The URL that is sent and logged is unchanged. - Streaming capture: a host streaming block that raises no longer leaks its message into the log, and a non-intercepted request made inside a streaming block no longer contributes its chunks to the outer request's log.
- Webhook helper methods are private.
intercept_batch_request,webhook_secretandprocess_eventare no longer public in the controller, so they cannot be routed to as actions.
🛠️ Fixed: body size caps and capture failures
max_captured_body_bytesnow caps response bodies (streamed or not) as well as requests. A response over the cap (default 1 MB) is logged as abody_too_largemarker; your app always receives the full response.- Batch items and chunked request bodies respect the cap. The OpenAI and Vertex batch processors cap each item, OpenAI result files are read line by line, and a malformed line is skipped instead of dropping the file. A chunked
body_streamrequest is no longer read ahead of the send. - A failure while recording a response or a streamed chunk is logged and swallowed instead of raising into your already-successful request. JSON nested more than 100 levels is logged as a marker instead of the raw string.
⚠️ Heads-up When Upgrading
-
Breaking:
Coolhand.public_content_serviceis nowCoolhand.help_center_service, andCoolhand::PublicContentServiceis nowCoolhand::HelpCenterService. The old names are removed with no alias. Signatures and requests are unchanged:help_center = Coolhand.help_center_service # was Coolhand.public_content_service
-
Unsigned webhook tests now get a 401 in development and test if a secret is set. Sign the payload (snippet in the OpenAI webhook docs), leave the secret unset in development, or replay a real delivery.
-
Webhook helpers are private. Define
webhook_secretunderprivatein your controller; callingintercept_batch_request,webhook_secretorprocess_eventfrom outside it now raisesNoMethodError, and those three names are reserved. -
Logged data changes: credential-named fields in bodies, SSE streams and forwarded webhooks, and URL fragments and more query params, now show
[REDACTED]or are dropped; responses over 1 MB appear as a marker;forward_webhookheaders no longer include Rack/CGI entries (remote-addr,query-string, ...). A plain header hash whose keys are all uppercase ({"ETAG" => ...}) is treated as a Rack env and its non-HTTP_*entries are dropped. -
Custom
webhook_id_storeimplementations now receive2 * webhook_replay_tolerance_secondsasttl_seconds, and may optionally implementrelease!(id).
🧹 Other Changes
- Docs:
docs/anthropic.mdnow describes the unifiedNetHttpInterceptorinstead of the removedFaradayInterceptor; the OpenAI examples usegpt-4o-mini. - CI:
ruby/setup-rubybumped to 1.335.0 for the Ruby 3.3.13 / 3.4.12 / 4.0.9 regexp security fixes. - Test coverage gaps closed (webhook secret handling, sanitizer negatives, batch wire payloads, oversized streamed responses).
📌 Known Follow-ups
Still open: #145 absolute-URL req.path matching, and #146 test coverage gaps (its listed items are now covered, left open for a final check). Not yet tracked as issues:
- Path matching is still case-sensitive (
/OpenAI/filesis not excluded), and;matrix params and reserved escapes such as%2Fare not normalized. credentialsis not in the body credential-key list (it can be a legitimate string), and a process env var starting withHTTP_(other thanHTTP_PROXY) still passes whenforward_webhookis given a Rack env.- The OpenAI and Vertex batch processors' error-handling branches (background-lane rescue, missing
openaigem) lack specs.