@askalf/dario 4.8.38 → 4.8.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -129,6 +129,7 @@ export declare function orderHeadersForOutbound(headers: Record<string, string>,
129
129
  */
130
130
  export declare function orderBodyForOutbound(body: Record<string, unknown>, overrideOrder?: string[] | undefined): Record<string, unknown>;
131
131
  export declare function scrubFrameworkIdentifiers(text: string): string;
132
+ export declare function scrubFrameworkIdentifiersInContent(text: string): string;
132
133
  /**
133
134
  * Detect text-tool-protocol clients (Cline, Kilo Code, Roo Code and
134
135
  * their forks) by fingerprinting the incoming system prompt.
@@ -247,9 +247,31 @@ const FRAMEWORK_PATTERNS = [
247
247
  // OC's sessions_* tool-name prefix — flagged as a fingerprint in dario#23.
248
248
  /\bsessions_[a-z_]+\b/gi,
249
249
  ];
250
- export function scrubFrameworkIdentifiers(text) {
250
+ // Patterns SAFE to apply to message *content* (user data: source code, docs,
251
+ // tool output). This is the small subset of FRAMEWORK_PATTERNS that consists
252
+ // only of distinctive, multi-word / unambiguous product identifiers which
253
+ // effectively never appear verbatim in real code or prose. It deliberately
254
+ // EXCLUDES every bare single-word pattern (`continue`, `cursor`, `gateway`,
255
+ // `openai`, `hermes`, `zed`, `tabby`, `cody`, `aider`, `cline`, `copilot`,
256
+ // `windsurf`, …) because those collide with ordinary code tokens and English.
257
+ // Stripping those from a user's payload silently CORRUPTS it: the JS keyword
258
+ // `continue;` became `;` (because Continue.dev is on the list), which made a
259
+ // code auditor report a bare-semicolon "no-op" that THIS PROXY had introduced.
260
+ // A proxy must never mutate the user's content — identity-masking of the
261
+ // *client's framing* is the job of the system-prompt scrub, which still uses
262
+ // the full FRAMEWORK_PATTERNS set.
263
+ const CONTENT_FRAMEWORK_PATTERNS = [
264
+ /\b(roo[- ]?cline|roo[- ]?code|big[- ]?agi|claude[- ]?bridge)\b/gi,
265
+ /\b(librechat|typingmind)\b/gi,
266
+ // NOTE: deliberately omits `/powered by [a-z]+/` and `/\bgateway\b/` etc.
267
+ // from FRAMEWORK_PATTERNS — those would strip legitimate user content like
268
+ // a "Powered by Stripe" footer or a `gateway` variable. Only distinctive,
269
+ // multi-token product names that never occur verbatim in real code/data are
270
+ // safe to mask in the user's payload.
271
+ ];
272
+ function scrubWithPatterns(text, patterns) {
251
273
  let result = text;
252
- for (const pattern of FRAMEWORK_PATTERNS) {
274
+ for (const pattern of patterns) {
253
275
  pattern.lastIndex = 0;
254
276
  result = result.replace(pattern, (match, ...args) => {
255
277
  const offset = args[args.length - 2];
@@ -271,6 +293,15 @@ export function scrubFrameworkIdentifiers(text) {
271
293
  }
272
294
  return result;
273
295
  }
296
+ // Scrub the CLIENT'S system prompt / identity fields — full pattern set.
297
+ export function scrubFrameworkIdentifiers(text) {
298
+ return scrubWithPatterns(text, FRAMEWORK_PATTERNS);
299
+ }
300
+ // Scrub message CONTENT (the user's code/data) — content-safe subset only, so
301
+ // a user's payload is never corrupted. See CONTENT_FRAMEWORK_PATTERNS.
302
+ export function scrubFrameworkIdentifiersInContent(text) {
303
+ return scrubWithPatterns(text, CONTENT_FRAMEWORK_PATTERNS);
304
+ }
274
305
  /**
275
306
  * Detect text-tool-protocol clients (Cline, Kilo Code, Roo Code and
276
307
  * their forks) by fingerprinting the incoming system prompt.
@@ -1237,25 +1268,29 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1237
1268
  // system array a second time per request. Scrub applies at this
1238
1269
  // point so framework identifiers don't leak upstream.
1239
1270
  let systemText = scrubFrameworkIdentifiers(rawSystemForDetection);
1240
- // Also scrub framework identifiers from message content text blocks.
1241
- // Clients often inject their product name into user/tool messages as well,
1242
- // and the system-prompt-only scrub used to miss those.
1271
+ // Also scrub framework identifiers from message content text blocks
1272
+ // clients can leak their product name in user/tool messages too. This uses
1273
+ // the CONTENT-SAFE subset (scrubFrameworkIdentifiersInContent), NOT the full
1274
+ // pattern set: message content is the user's own code/data and must never be
1275
+ // mutated. The full set ran here previously and corrupted source — the JS
1276
+ // keyword `continue;` became `;` (Continue.dev is a scrubbed name), so a code
1277
+ // auditor "found" a bare-semicolon no-op the proxy itself had introduced.
1243
1278
  for (const msg of messages) {
1244
1279
  if (typeof msg.content === 'string') {
1245
- msg.content = scrubFrameworkIdentifiers(msg.content);
1280
+ msg.content = scrubFrameworkIdentifiersInContent(msg.content);
1246
1281
  }
1247
1282
  else if (Array.isArray(msg.content)) {
1248
1283
  for (const block of msg.content) {
1249
1284
  if (block.type === 'text' && typeof block.text === 'string') {
1250
- block.text = scrubFrameworkIdentifiers(block.text);
1285
+ block.text = scrubFrameworkIdentifiersInContent(block.text);
1251
1286
  }
1252
1287
  if (block.type === 'tool_result' && typeof block.content === 'string') {
1253
- block.content = scrubFrameworkIdentifiers(block.content);
1288
+ block.content = scrubFrameworkIdentifiersInContent(block.content);
1254
1289
  }
1255
1290
  if (block.type === 'tool_result' && Array.isArray(block.content)) {
1256
1291
  for (const sub of block.content) {
1257
1292
  if (sub.type === 'text' && typeof sub.text === 'string') {
1258
- sub.text = scrubFrameworkIdentifiers(sub.text);
1293
+ sub.text = scrubFrameworkIdentifiersInContent(sub.text);
1259
1294
  }
1260
1295
  }
1261
1296
  }
package/dist/proxy.d.ts CHANGED
@@ -193,6 +193,17 @@ interface ProxyOptions {
193
193
  * Sourced from `--system-prompt=<value>` or DARIO_SYSTEM_PROMPT.
194
194
  */
195
195
  systemPrompt?: string;
196
+ /**
197
+ * Upstream auth override: forward to api.anthropic.com using `x-api-key:
198
+ * <this>` (the per-token API pool) instead of the Pro/Max OAuth bearer.
199
+ * When set, OAuth/getAccessToken and the account pool are bypassed entirely
200
+ * — dario becomes a thin per-token Anthropic proxy. Default (unset) keeps
201
+ * the subscription-OAuth behavior. Used by the self-hosted compat workflow
202
+ * so it can route the suite THROUGH dario without tripping the subscription
203
+ * pool's ~3/min cap. Sourced from ANTHROPIC_UPSTREAM_API_KEY (env-only — never
204
+ * a CLI flag, so the key never lands in `ps`/argv).
205
+ */
206
+ upstreamApiKey?: string;
196
207
  /**
197
208
  * Overage-guard — halt the proxy on the first response carrying
198
209
  * `representative-claim: overage`. Subscribers should never see a
@@ -253,5 +264,11 @@ export declare function authenticateRequest(headers: IncomingMessage['headers'],
253
264
  * user's real credential for some other provider. Pure over inputs (dario#97).
254
265
  */
255
266
  export declare function describeAuthReject(headers: IncomingMessage['headers']): string;
267
+ /**
268
+ * Build the upstream auth header for the request to api.anthropic.com.
269
+ * `upstreamApiKey` set → per-token API pool (`x-api-key`); otherwise the
270
+ * Pro/Max OAuth bearer. Pure + exported for unit testing.
271
+ */
272
+ export declare function upstreamAuthHeaders(upstreamApiKey: string, accessToken: string): Record<string, string>;
256
273
  export declare function startProxy(opts?: ProxyOptions): Promise<void>;
257
274
  export {};
package/dist/proxy.js CHANGED
@@ -448,11 +448,27 @@ function enrich429(body, headers) {
448
448
  return body;
449
449
  }
450
450
  }
451
+ /**
452
+ * Build the upstream auth header for the request to api.anthropic.com.
453
+ * `upstreamApiKey` set → per-token API pool (`x-api-key`); otherwise the
454
+ * Pro/Max OAuth bearer. Pure + exported for unit testing.
455
+ */
456
+ export function upstreamAuthHeaders(upstreamApiKey, accessToken) {
457
+ return upstreamApiKey
458
+ ? { 'x-api-key': upstreamApiKey }
459
+ : { 'Authorization': `Bearer ${accessToken}` };
460
+ }
451
461
  export async function startProxy(opts = {}) {
452
462
  const port = opts.port ?? DEFAULT_PORT;
453
463
  const host = opts.host ?? process.env.DARIO_HOST ?? DEFAULT_HOST;
454
464
  const verbose = opts.verbose ?? false;
455
465
  const passthrough = opts.passthrough ?? false;
466
+ // Upstream auth override: a per-token API key forwards to the standard API
467
+ // pool via `x-api-key`, bypassing OAuth/Max + the account pool entirely.
468
+ // Env-only so the key never lands in `ps`/argv. Default (empty) = OAuth/Max.
469
+ const upstreamApiKey = (opts.upstreamApiKey ?? process.env.ANTHROPIC_UPSTREAM_API_KEY ?? '').trim();
470
+ if (upstreamApiKey)
471
+ console.error('[dario] upstream auth: per-token API key (x-api-key) — OAuth/Max + account pool bypassed');
456
472
  // DNS result order — prefer IPv4 for the Anthropic upstream by default.
457
473
  // api.anthropic.com publishes both A and AAAA records. In a container with
458
474
  // no IPv6 egress (e.g. a default Docker bridge network), Node's `verbatim`
@@ -1218,7 +1234,13 @@ export async function startProxy(opts = {}) {
1218
1234
  // requests, not within a single 429 retry.
1219
1235
  let poolAccount = null;
1220
1236
  let accessToken;
1221
- if (pool) {
1237
+ if (upstreamApiKey) {
1238
+ // Per-token API-key mode: no OAuth, no pool. `poolAccount` stays null,
1239
+ // so every pool-failover retry below is skipped; the x-api-key is set
1240
+ // on the outbound headers instead of an Authorization bearer.
1241
+ accessToken = '';
1242
+ }
1243
+ else if (pool) {
1222
1244
  poolAccount = pool.select();
1223
1245
  if (!poolAccount) {
1224
1246
  res.writeHead(503, JSON_HEADERS);
@@ -1612,7 +1634,7 @@ export async function startProxy(opts = {}) {
1612
1634
  }
1613
1635
  const headers = {
1614
1636
  ...staticHeaders,
1615
- 'Authorization': `Bearer ${accessToken}`,
1637
+ ...upstreamAuthHeaders(upstreamApiKey, accessToken),
1616
1638
  'x-claude-code-session-id': outboundSessionId,
1617
1639
  'anthropic-version': passthrough ? (req.headers['anthropic-version'] || '2023-06-01') : '2023-06-01',
1618
1640
  'anthropic-beta': beta,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "4.8.38",
3
+ "version": "4.8.40",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {