@askalf/dario 5.5.67 → 5.5.69
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.
- package/dist/accounts.js +7 -1
- package/dist/oauth.d.ts +25 -0
- package/dist/oauth.js +34 -0
- package/package.json +1 -1
package/dist/accounts.js
CHANGED
|
@@ -23,7 +23,7 @@ import { homedir } from 'node:os';
|
|
|
23
23
|
import { randomUUID, randomBytes, createHash } from 'node:crypto';
|
|
24
24
|
import { createServer } from 'node:http';
|
|
25
25
|
import { detectCCOAuthConfig } from './cc-oauth-detect.js';
|
|
26
|
-
import { loadCredentials, saveCredentialsTokens, buildManualAuthorizeUrl, parseManualPaste, readLineFromStdin, enumerateKeychainCredentials } from './oauth.js';
|
|
26
|
+
import { loadCredentials, saveCredentialsTokens, buildManualAuthorizeUrl, parseManualPaste, readLineFromStdin, enumerateKeychainCredentials, tokenRefreshDisabled, refreshDisabledError } from './oauth.js';
|
|
27
27
|
import { openBrowser } from './open-browser.js';
|
|
28
28
|
import { redactSecrets } from './redact.js';
|
|
29
29
|
import { durableWriteFile } from './durable-write.js';
|
|
@@ -137,6 +137,12 @@ export async function detectClaudeIdentity() {
|
|
|
137
137
|
const accountRefreshesInFlight = new Map();
|
|
138
138
|
/** Refresh an account's OAuth token using dario's auto-detected CC OAuth config. */
|
|
139
139
|
export async function refreshAccountToken(creds) {
|
|
140
|
+
// Borrow-don't-rotate: the pool path needs the same guard as the legacy one
|
|
141
|
+
// in oauth.ts, and for the same reason — a rotation here logs out every
|
|
142
|
+
// other holder of this account. Guarding only one of the two paths would
|
|
143
|
+
// leave the hole open for whichever store the process happens to load.
|
|
144
|
+
if (tokenRefreshDisabled())
|
|
145
|
+
throw refreshDisabledError();
|
|
140
146
|
const inFlight = accountRefreshesInFlight.get(creds.alias);
|
|
141
147
|
if (inFlight)
|
|
142
148
|
return inFlight;
|
package/dist/oauth.d.ts
CHANGED
|
@@ -188,6 +188,31 @@ export declare function readLineFromStdin(prompt: string): Promise<string>;
|
|
|
188
188
|
* doomed retries and masking as healthy.
|
|
189
189
|
*/
|
|
190
190
|
export declare function isTerminalRefreshFailure(status: number, body: string): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Borrow-don't-rotate mode. `DARIO_NO_TOKEN_REFRESH=1` makes every refresh
|
|
193
|
+
* path throw instead of rotating.
|
|
194
|
+
*
|
|
195
|
+
* WHY THIS EXISTS. Anthropic invalidates the previous refresh_token on every
|
|
196
|
+
* refresh (see the distributed-lock note in accounts.ts). So a process that
|
|
197
|
+
* merely READS a shared credential store is harmless, but the moment it
|
|
198
|
+
* refreshes, whatever else holds that token — a production proxy, another
|
|
199
|
+
* box — is silently logged out. Copying the file first does not help: the
|
|
200
|
+
* rotation happens upstream, so the copy invalidates the original too.
|
|
201
|
+
*
|
|
202
|
+
* That is not hypothetical. On 2026-08-25 a CI job on the self-hosted runner
|
|
203
|
+
* started a proxy against the shared store, refreshed it, and took the fleet
|
|
204
|
+
* down for roughly three hours with `invalid_grant`. The runner shares a box
|
|
205
|
+
* with prod and `/root/.dario` is a symlink into prod's store, so "just point
|
|
206
|
+
* HOME somewhere else" only helps when the job needs no real auth at all.
|
|
207
|
+
*
|
|
208
|
+
* Jobs that genuinely need subscription OAuth (the billing-classifier canary
|
|
209
|
+
* cannot use an API key — an API key IS the other billing bucket, so it would
|
|
210
|
+
* test the wrong thing) can now borrow the credential read-only: requests work
|
|
211
|
+
* for as long as the access token is valid, and an expiry fails LOUDLY here
|
|
212
|
+
* rather than quietly rotating a token that production is holding.
|
|
213
|
+
*/
|
|
214
|
+
export declare function tokenRefreshDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
215
|
+
export declare function refreshDisabledError(): Error;
|
|
191
216
|
export declare function refreshTokens(): Promise<OAuthTokens>;
|
|
192
217
|
/**
|
|
193
218
|
* Get a valid access token, refreshing if needed.
|
package/dist/oauth.js
CHANGED
|
@@ -769,7 +769,41 @@ export async function readLineFromStdin(prompt) {
|
|
|
769
769
|
export function isTerminalRefreshFailure(status, body) {
|
|
770
770
|
return status === 401 || status === 403 || /invalid_grant/i.test(body);
|
|
771
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Borrow-don't-rotate mode. `DARIO_NO_TOKEN_REFRESH=1` makes every refresh
|
|
774
|
+
* path throw instead of rotating.
|
|
775
|
+
*
|
|
776
|
+
* WHY THIS EXISTS. Anthropic invalidates the previous refresh_token on every
|
|
777
|
+
* refresh (see the distributed-lock note in accounts.ts). So a process that
|
|
778
|
+
* merely READS a shared credential store is harmless, but the moment it
|
|
779
|
+
* refreshes, whatever else holds that token — a production proxy, another
|
|
780
|
+
* box — is silently logged out. Copying the file first does not help: the
|
|
781
|
+
* rotation happens upstream, so the copy invalidates the original too.
|
|
782
|
+
*
|
|
783
|
+
* That is not hypothetical. On 2026-08-25 a CI job on the self-hosted runner
|
|
784
|
+
* started a proxy against the shared store, refreshed it, and took the fleet
|
|
785
|
+
* down for roughly three hours with `invalid_grant`. The runner shares a box
|
|
786
|
+
* with prod and `/root/.dario` is a symlink into prod's store, so "just point
|
|
787
|
+
* HOME somewhere else" only helps when the job needs no real auth at all.
|
|
788
|
+
*
|
|
789
|
+
* Jobs that genuinely need subscription OAuth (the billing-classifier canary
|
|
790
|
+
* cannot use an API key — an API key IS the other billing bucket, so it would
|
|
791
|
+
* test the wrong thing) can now borrow the credential read-only: requests work
|
|
792
|
+
* for as long as the access token is valid, and an expiry fails LOUDLY here
|
|
793
|
+
* rather than quietly rotating a token that production is holding.
|
|
794
|
+
*/
|
|
795
|
+
export function tokenRefreshDisabled(env = process.env) {
|
|
796
|
+
return env.DARIO_NO_TOKEN_REFRESH === '1';
|
|
797
|
+
}
|
|
798
|
+
export function refreshDisabledError() {
|
|
799
|
+
return new Error('token refresh is disabled (DARIO_NO_TOKEN_REFRESH=1) and the access token needs renewing. ' +
|
|
800
|
+
'This process is borrowing a shared credential read-only: refreshing would rotate the token ' +
|
|
801
|
+
'out from under whoever else holds it. Re-run once the credential owner has refreshed, or ' +
|
|
802
|
+
'unset DARIO_NO_TOKEN_REFRESH if this process is the owner.');
|
|
803
|
+
}
|
|
772
804
|
export async function refreshTokens() {
|
|
805
|
+
if (tokenRefreshDisabled())
|
|
806
|
+
throw refreshDisabledError();
|
|
773
807
|
// Prevent concurrent refreshes — if one is already in progress, wait for it
|
|
774
808
|
if (refreshInProgress)
|
|
775
809
|
return refreshInProgress;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.69",
|
|
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": {
|