You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
[0.14.0] - 2026-09-20
What's New
Nine new built-in providers — DeepSeek (api.deepseek.com), Mistral (api.mistral.ai), Perplexity (api.perplexity.ai), xAI (api.x.ai), Cohere (api.cohere.com/api.cohere.ai, v2 chat and v1/v2 embed only), TypeSafe Jev (api.typesafe.ai, the /v1/systemone endpoint only), Ollama (port 11434 or ollama.com, chat/generate/embed endpoints only), Amazon Bedrock (bedrock-runtime[-fips].<region>.amazonaws.com[.cn], /model/ and /openai/ paths only) and ElevenLabs (api.elevenlabs.io) are now monitored out of the box, in both Node.js and Edge runtimes. (#247, #248)
Azure inference coverage — Azure OpenAI dedicated hosts (openai.azure.com/.us/.cn), Azure AI Services and AI Foundry (cognitiveservices.azure.com/.us/.cn, services.ai.azure.com/.us, including Foundry project-scoped /api/projects/<project>/openai/... URLs), Azure AI Foundry serverless endpoints (inference.ai.azure.com, models.ai.azure.com) and Azure Machine Learning managed online endpoints (inference.ml.azure.com/.us) are now built-in providers. Previously the only Azure host recognized was models.inference.ai.azure.com, under GitHub Models. (#245, #246)
New optional pattern fields for custom patternsFile entries: requiresPathMatch, ports, and * in domains.requiresPathMatch: true makes a pattern's paths binding — the request path must start with one of them, ending on a path-segment boundary. ports lets a provider be identified by port on any host (used by Ollama's 11434), and * in a domain stands for exactly one DNS label (e.g. bedrock-runtime.*.amazonaws.com). All three are opt-in: every existing pattern, including custom ones, matches exactly as before. See docs/global-monitoring.md. (#248)
Note: ElevenLabs and Azure Machine Learning are matched host-wide (no path can distinguish inference traffic there), Ollama only covers its native /api/* endpoints on port 11434 or ollama.com, and the Azure OpenAI control-plane paths (/openai/files, /openai/batches, /openai/fine_tuning, /openai/models) are excluded by default so file uploads and models.list() aren't logged as inference. Request-option hostnames (https.request({ hostname: ... })) are now matched case-insensitively, same as URL-derived hostnames.
Upgrade Notes
More headers now show as [REDACTED] in logs. Header redaction is name-based: anything containing auth, key, token, secret or cookie (e.g. x-auth-token, www-authenticate) is redacted; rate-limit headers such as anthropic-ratelimit-tokens-remaining are exempt.
baseUrl with user:pass@ userinfo, a query string or a fragment now throws at construction (it could never work: the API path was swallowed into the query, or every call failed). Use the apiKey option for credentials.
Coolhand API read responses that aren't JSON objects (null, scalars) now throw, and createClientFile throws on metadata keys containing [, ] or control characters (they would open extra nesting levels in the multipart field name).
When auto-monitor has already patched http/https/fetch, new Coolhand() no longer patches again, so that instance's excludeApiPatterns/baseUrl/apiKey are not used for interception (it warns unless silent). Previously both layers wrapped every call and logged each request twice.
FormData, Blob and stream fetch() request bodies are logged as null (previously the useless string "[object FormData]"); typed-array bodies are now decoded as text instead of "123,34,...".
A custom patternsFile that native ESM builds silently ignored is now applied (Node 20.16+/22.3+; older Node in native ESM warns and keeps the built-in patterns, tracked in #253).
Every Coolhand API call now has a timeout (30s; 10 minutes for multipart uploads), so a hung endpoint can no longer keep pending log payloads alive forever.
Security
Ocp-Apim-Subscription-Key and subscription-key are now redacted from headers and query params. Azure AI Services calls authenticated with Azure API Management's subscription key had it forwarded to Coolhand in cleartext, while api-key/authorization on the same endpoint were already redacted. (#245)
New PatternMatchingService#sanitizeBody() redacts credentials from captured request and response bodies. Azure OpenAI's "On Your Data" feature embeds datastore credentials (an Azure AI Search admin key, a Cosmos DB or MongoDB connection string) under data_sources/dataSources; inside that subtree any key containing key, secret, password, token or connectionstring (case- and separator-insensitive) is redacted. Anywhere in a body it also redacts Anthropic mcp_servers[].authorization_token, OpenAI realtime client_secret (an ephemeral key returned in response bodies), and authorization/headers on OpenAI Responses MCP tool definitions. Bodies that aren't a single JSON document (NDJSON, a BOM-prefixed body, a top-level array, or one truncated at the 50 MB cap) are scrubbed by key name in linear time instead of being logged verbatim. Wired into both the http/https and fetch interception paths, in both the Coolhand class and the global monitor. (#245, #250)
xi-api-key (ElevenLabs) is now in DEFAULT_REDACTED_HEADERS, so a custom patternsFile entry for that host without a headers map can't leak it — the same defense-in-depth as x-goog-api-key and cf-aig-authorization before it.
Array-form request headers are now redacted.http.request/https.request accept options.headers as an array — flat (['Authorization', 'Bearer ...']) or as [name, value] pairs — but sanitizeHeaders ran Object.entries() over it, so the keys were array indexes and no credential header ever matched: a host app using that (valid) calling convention had its Authorization/x-api-key logged to Coolhand in cleartext.
The global monitor no longer logs the raw request body when sanitizeBody fails.sanitizeBody returns null when redaction itself throws (e.g. a request body nested deeply enough to overflow the stack), but both interception paths in global-monitor.ts then substituted the original body via ?? parsedRequestBody — logging exactly the credentials it could not strip.
sanitizeURL now strips user:pass@ userinfo and redacts more credential query params. A https://user:pass@host/... URL was logged with its credentials intact (reachable through the new port-based Ollama pattern), and the query-param list missed ocp-apim-subscription-key, api-key, x-api-key, client_secret, refresh_token, id_token and access-token.
A custom patternsFile with wrongly-typed paths or ports (or a non-string domains entry) now falls back to the built-in patterns, like any other malformed file. Previously the entry loaded, then threw inside the request matcher on every call, disabling matching for every pattern listed after it.
Further redaction gaps closed after a second review pass. Header names containing passw, credential, bearer, jwt or signature are now redacted; the query-param list adds api_token, auth_token, bearer_token, secret_key, private_key, access_key, auth, bearer and pwd; and inside data_sources, passwd, pwd and credential keys are redacted too.
sanitizeURL and sanitizeHeaders are now linear-time. A URL with ~100,000 repeated sensitive params took ~7.5s (URLSearchParams#set is quadratic on duplicates), and 40,000 repeated header names took ~10s — a synchronous stall of the host's event loop on the request hot path. Both now complete in milliseconds.
baseUrl validation errors no longer echo credentials. A scheme-less value (user:pass@host), leading whitespace, a password containing @, or a query string/fragment was previously quoted verbatim into the thrown error.
The Coolhand API's error response body is now truncated to 2,000 characters in the log line and thrown error, matching the existing cap on its read paths, so a misbehaving endpoint can't flood the host's logs.
Bug Fixes
The interceptor can no longer break or delay the host's own call. Capture failures are now swallowed instead of thrown into the host: req.end(callback) (a function as the first argument) no longer throws before the request is sent, fetch(url, null) no longer throws, and an error while capturing a fetch() response is no longer rethrown after a successful upstream call. The host-facing response tee now caps buffered bytes rather than lifetime bytes, so a prompt reader downloading a body larger than 50 MB is no longer cut off mid-body. (#250)
Aborted or destroyed http/https responses are now logged once and their dedup entry released, instead of being lost when end never fires. A request 'error' is re-thrown when Coolhand's is the only listener, matching Node's default instead of leaving the host's promise hanging.
Native ESM builds now honor patternsFile/COOLHAND_PATTERNS_FILE (see Upgrade Notes above).
Host matching: a trailing-dot FQDN (api.openai.com.) now matches, options.path is normalized like URL paths (//model/x, /a/../b) before requiresPathMatch is checked, and wildcard-only domains entries (*, **) are ignored instead of matching every host.
Request-body encoding was ignored when capturing req.write/req.end strings (e.g. hex), corrupting the logged body. (#227)
Maintenance
Domain matching now lives in one findDomainMatch helper shared by the async, sync and URL-based matchers (previously three duplicated loops), and a new test/provider-patterns.test.ts runs the same URL matrix through both the api-patterns.json patterns and the Edge-runtime fallback to catch drift between them.
The example scripts in examples/ now prove log delivery, not just interception.basic.js and feedback-example.js join openai-example.js/anthropic-example.js as live smoke tests against real provider/Coolhand API calls; anthropic-streaming.js gained an opt-in delivery-confirmation step. See examples/README.md.