@skrr-ai/cli 0.1.81 → 0.1.82
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/base-command.d.ts +16 -0
- package/dist/base-command.js +124 -9
- package/dist/commands/followups/checkin.js +1 -1
- package/dist/commands/login.js +10 -0
- package/dist/commands/logout.js +4 -0
- package/dist/commands/store/installs.js +6 -1
- package/dist/lib/auth-storage.d.ts +5 -0
- package/dist/lib/auth-storage.js +8 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/credential-resolver.js +7 -1
- package/dist/lib/daemonBroker.d.ts +35 -0
- package/dist/lib/daemonBroker.js +187 -11
- package/dist/lib/daemonBrokerRefusal.d.ts +14 -0
- package/dist/lib/daemonBrokerRefusal.js +18 -1
- package/dist/lib/harness-context.d.ts +8 -0
- package/dist/lib/harness-context.js +55 -0
- package/dist/lib/login.js +4 -1
- package/dist/lib/refresh.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +25 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +35 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +25 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3856 -3801
- package/oclif.manifest.json +15792 -15792
- package/package.json +2 -2
package/dist/lib/daemonBroker.js
CHANGED
|
@@ -70,6 +70,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
70
70
|
return result;
|
|
71
71
|
};
|
|
72
72
|
})();
|
|
73
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
74
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
75
|
+
};
|
|
73
76
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
77
|
exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR = exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = void 0;
|
|
75
78
|
exports.normalizeBaseUrl = normalizeBaseUrl;
|
|
@@ -89,16 +92,19 @@ exports.findBrokeredHandoffDescriptor = findBrokeredHandoffDescriptor;
|
|
|
89
92
|
exports.maybeRetireStoredCliAuthFile = maybeRetireStoredCliAuthFile;
|
|
90
93
|
exports.shouldBrokerBeforeStoredCredential = shouldBrokerBeforeStoredCredential;
|
|
91
94
|
exports.attemptDaemonBrokerLoginAndPersist = attemptDaemonBrokerLoginAndPersist;
|
|
95
|
+
exports.cliSessionAdoptionBlocker = cliSessionAdoptionBlocker;
|
|
92
96
|
exports.maybeAutoBroker = maybeAutoBroker;
|
|
93
97
|
const fs = __importStar(require("node:fs"));
|
|
94
98
|
const path = __importStar(require("node:path"));
|
|
95
99
|
const os = __importStar(require("node:os"));
|
|
100
|
+
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
96
101
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
97
102
|
const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
|
|
98
103
|
const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
|
|
99
104
|
const brokered_dpop_1 = require("./brokered-dpop");
|
|
100
105
|
const auth_storage_1 = require("./auth-storage");
|
|
101
106
|
const config_1 = require("./config");
|
|
107
|
+
const harness_context_1 = require("./harness-context");
|
|
102
108
|
const cli_identity_1 = require("./cli-identity");
|
|
103
109
|
const daemonRestartWait_1 = require("./daemonRestartWait");
|
|
104
110
|
const keychain_1 = require("./keychain");
|
|
@@ -562,8 +568,19 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
|
|
|
562
568
|
// would either break explicit login or be bypassed by anyone who calls it
|
|
563
569
|
// the way login does. It is not a boundary, so it is not attempted. What the
|
|
564
570
|
// CLI CAN guarantee is that its own automatic path never mints and persists
|
|
565
|
-
// a refresh family: when `access_token` is advertised,
|
|
566
|
-
// the answer, and the daemon's outage is reported with its
|
|
571
|
+
// a refresh family on a borrower's behalf: when `access_token` is advertised,
|
|
572
|
+
// a failed redeem is the answer, and the daemon's outage is reported with its
|
|
573
|
+
// repair.
|
|
574
|
+
//
|
|
575
|
+
// OSK-13254 narrowed "the automatic path" to mean a BORROWER. A person's
|
|
576
|
+
// first `skrr` on a profile with nothing stored now ADOPTS a CLI session of
|
|
577
|
+
// its own (`maybeAutoBroker` with `adopt`, `preferHandoffMode:
|
|
578
|
+
// 'refresh_family'`), because a sign-in that lived only as long as the daemon
|
|
579
|
+
// read as "signed out" every time the daemon restarted. What OSK-12018 was
|
|
580
|
+
// guarding against — an agent-originated implicit sign-in persisting a family
|
|
581
|
+
// — is now refused by name: a process the daemon started for an agent or tool
|
|
582
|
+
// carries `SKRR_DAEMON_CHILD` / `OVERSKY_AUTONOMOUS_SESSION`
|
|
583
|
+
// (`harness-context.ts`), sets `borrowOnly`, and `writeToBackend` refuses it.
|
|
567
584
|
//
|
|
568
585
|
// COMPATIBILITY WINDOW: a v1 descriptor (no `handoffModes`, so no
|
|
569
586
|
// `access_token`) still takes the durable mint on the automatic path below,
|
|
@@ -578,7 +595,17 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
|
|
|
578
595
|
const offersAccessToken = (0, cli_handoff_wire_1.descriptorOffersAccessToken)(bootstrap);
|
|
579
596
|
const offersRefreshFamily = modes.includes('refresh_family');
|
|
580
597
|
const preferAccessToken = opts.preferHandoffMode === 'access_token';
|
|
581
|
-
if (
|
|
598
|
+
if (opts.borrowOnly && !offersAccessToken) {
|
|
599
|
+
return {
|
|
600
|
+
ok: false,
|
|
601
|
+
reason: 'unknown',
|
|
602
|
+
refusedBy: 'daemon',
|
|
603
|
+
code: 'HANDOFF_BORROW_UNAVAILABLE',
|
|
604
|
+
detail: 'The skrr background service on this computer is too old to lend this agent process a ' +
|
|
605
|
+
'short-lived token, and a process it started for an agent never stores a sign-in. Update skrrd.',
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (offersAccessToken && (preferAccessToken || opts.borrowOnly || !offersRefreshFamily)) {
|
|
582
609
|
const redeem = await redeemCliHandoffToken(bootstrap, {
|
|
583
610
|
forceRefresh: false,
|
|
584
611
|
timeoutMs: opts.timeoutMs,
|
|
@@ -1150,6 +1177,16 @@ async function attemptDaemonBrokerLoginAndPersist(opts) {
|
|
|
1150
1177
|
// Callers use the token in-memory for this process only.
|
|
1151
1178
|
if (outcome.brokered)
|
|
1152
1179
|
return outcome;
|
|
1180
|
+
// Unreachable through `brokerThrough`, which never mints for a borrower;
|
|
1181
|
+
// kept so a future route cannot persist on a borrower's behalf.
|
|
1182
|
+
if (opts.borrowOnly) {
|
|
1183
|
+
return {
|
|
1184
|
+
ok: false,
|
|
1185
|
+
reason: 'unknown',
|
|
1186
|
+
code: 'HANDOFF_BORROW_UNAVAILABLE',
|
|
1187
|
+
detail: 'A borrower was handed a durable credential; it was not stored.',
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1153
1190
|
const serverOrigin = normalizeBaseUrl(opts.baseURL);
|
|
1154
1191
|
if (!serverOrigin) {
|
|
1155
1192
|
throw new Error('Daemon-broker credential persistence requires a valid CLI baseURL.');
|
|
@@ -1199,6 +1236,123 @@ const AUTH_SELF_MANAGED_COMMANDS = new Set([
|
|
|
1199
1236
|
'pair',
|
|
1200
1237
|
'generate-recovery-code',
|
|
1201
1238
|
]);
|
|
1239
|
+
/**
|
|
1240
|
+
* Why this invocation must borrow instead of adopting a CLI session of its
|
|
1241
|
+
* own, or null when it may adopt (OSK-13254).
|
|
1242
|
+
*/
|
|
1243
|
+
function cliSessionAdoptionBlocker(cliConfig, env = process.env) {
|
|
1244
|
+
if ((0, harness_context_1.isHarnessCliContext)(env))
|
|
1245
|
+
return 'harness_context';
|
|
1246
|
+
if (env.SKRR_NO_CLI_SESSION_ADOPTION === '1')
|
|
1247
|
+
return 'opted_out';
|
|
1248
|
+
// `skrr logout` said "this CLI is signed out". The daemon may still LEND
|
|
1249
|
+
// tokens (logout says so), but a durable session does not reappear behind
|
|
1250
|
+
// that statement; the next `skrr login` clears the flag.
|
|
1251
|
+
if (cliConfig.adoptDaemonSession === false)
|
|
1252
|
+
return 'signed_out';
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Failures that mean no daemon answered (or answered for another server) —
|
|
1257
|
+
* a second attempt on the token route cannot do better and would only wait
|
|
1258
|
+
* out the restart window twice.
|
|
1259
|
+
*/
|
|
1260
|
+
function daemonDidNotAnswer(failure) {
|
|
1261
|
+
switch (failure.reason) {
|
|
1262
|
+
case 'no_bootstrap':
|
|
1263
|
+
case 'stale_bootstrap':
|
|
1264
|
+
case 'bootstrap_parse':
|
|
1265
|
+
case 'base_url_mismatch':
|
|
1266
|
+
case 'network':
|
|
1267
|
+
case 'timeout':
|
|
1268
|
+
return true;
|
|
1269
|
+
default:
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/** The adoption lock: one per config root and profile. */
|
|
1274
|
+
function adoptionLockPath(profile) {
|
|
1275
|
+
const safe = profile.replace(/[^A-Za-z0-9._-]/g, '_') || 'default';
|
|
1276
|
+
return path.join((0, config_1.configRoot)(), `cli-session-adopt.${safe}.lock`);
|
|
1277
|
+
}
|
|
1278
|
+
function storedAccessToken(baseURL) {
|
|
1279
|
+
try {
|
|
1280
|
+
return (0, auth_storage_1.readFromBackend)(baseURL).bundle?.token ?? null;
|
|
1281
|
+
}
|
|
1282
|
+
catch {
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Adopt a CLI session through the local daemon (OSK-13254): the durable mint
|
|
1288
|
+
* (`/v1/auth/cli-handoff` → `POST /api/daemons/cli-handoff`), persisted like a
|
|
1289
|
+
* browser login, so that the next `skrr` reads it from the Keychain/file and
|
|
1290
|
+
* renews it itself — the daemon restarting no longer reads as signed out.
|
|
1291
|
+
*
|
|
1292
|
+
* Serialized across processes. The confirm that follows a mint retires every
|
|
1293
|
+
* OTHER live family of this cliId, so two concurrent adopters would each
|
|
1294
|
+
* revoke the other's session mid-command. Under the lock a process that
|
|
1295
|
+
* finds a session a sibling stored while it waited uses that one instead.
|
|
1296
|
+
*
|
|
1297
|
+
* Never worse than borrowing: if the lock cannot be taken in time, or the
|
|
1298
|
+
* daemon answered but would not mint (rate limit, a server refusal, a
|
|
1299
|
+
* broker-only descriptor), this borrows an access token for this process
|
|
1300
|
+
* exactly as before.
|
|
1301
|
+
*/
|
|
1302
|
+
async function adoptCliSessionThroughDaemon(base, lockRetries) {
|
|
1303
|
+
const profile = base.profile ?? 'default';
|
|
1304
|
+
const baseURL = base.baseURL ?? '';
|
|
1305
|
+
const borrow = () => attemptDaemonBrokerLoginAndPersist({
|
|
1306
|
+
...base,
|
|
1307
|
+
preferHandoffMode: 'access_token',
|
|
1308
|
+
borrowOnly: true,
|
|
1309
|
+
});
|
|
1310
|
+
// Adopt only from a live descriptor that can mint a family. One that
|
|
1311
|
+
// cannot (the Dedicated guest's access-token-only shape), or none at all (a
|
|
1312
|
+
// daemon that is down or restarting), has nothing to adopt: borrow without
|
|
1313
|
+
// taking the lock, so concurrent commands never queue behind it and the
|
|
1314
|
+
// borrow path's own restart wait and outage reporting apply unchanged.
|
|
1315
|
+
const usable = listUsableBootstraps(base);
|
|
1316
|
+
if (!usable.some(({ bootstrap }) => (0, cli_handoff_wire_1.advertisedHandoffModes)(bootstrap).includes('refresh_family'))) {
|
|
1317
|
+
return { outcome: await borrow() };
|
|
1318
|
+
}
|
|
1319
|
+
const before = storedAccessToken(baseURL);
|
|
1320
|
+
const lockPath = adoptionLockPath(profile);
|
|
1321
|
+
let release = null;
|
|
1322
|
+
try {
|
|
1323
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true, mode: 0o700 });
|
|
1324
|
+
if (!fs.existsSync(lockPath))
|
|
1325
|
+
fs.writeFileSync(lockPath, '', { mode: 0o600 });
|
|
1326
|
+
release = await proper_lockfile_1.default.lock(lockPath, {
|
|
1327
|
+
// Longer than one mint (20s loopback + 10s confirm), so a holder that
|
|
1328
|
+
// is still working is never judged stale.
|
|
1329
|
+
stale: 60_000,
|
|
1330
|
+
retries: { retries: lockRetries, minTimeout: 250, maxTimeout: 1_000, factor: 1.5 },
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
catch {
|
|
1334
|
+
// Could not serialize: do not race a sibling's mint. Borrow.
|
|
1335
|
+
return { outcome: await borrow() };
|
|
1336
|
+
}
|
|
1337
|
+
let durable;
|
|
1338
|
+
try {
|
|
1339
|
+
const now = storedAccessToken(baseURL);
|
|
1340
|
+
if (now && now !== before)
|
|
1341
|
+
return { adoptedBySibling: true };
|
|
1342
|
+
durable = await attemptDaemonBrokerLoginAndPersist({
|
|
1343
|
+
...base,
|
|
1344
|
+
preferHandoffMode: 'refresh_family',
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
finally {
|
|
1348
|
+
await release().catch(() => undefined);
|
|
1349
|
+
}
|
|
1350
|
+
if (durable.ok || daemonDidNotAnswer(durable))
|
|
1351
|
+
return { outcome: durable };
|
|
1352
|
+
// The daemon answered but would not mint a family. The token route is a
|
|
1353
|
+
// different door with different limits; take it for this process.
|
|
1354
|
+
return { outcome: await borrow() };
|
|
1355
|
+
}
|
|
1202
1356
|
/**
|
|
1203
1357
|
* Auto-broker pre-flight for `BaseCommand.init()`. When the credential
|
|
1204
1358
|
* resolver returns no token, this attempts a silent daemon broker so
|
|
@@ -1244,21 +1398,43 @@ async function maybeAutoBroker(opts) {
|
|
|
1244
1398
|
// confirm cannot retire another profile's session. Persisted by the caller on
|
|
1245
1399
|
// success so the rotation row stays bound across processes.
|
|
1246
1400
|
const { cliId, updatedConfig } = (0, cli_identity_1.planLoginCliId)(opts.cliConfig);
|
|
1247
|
-
const
|
|
1401
|
+
const base = {
|
|
1248
1402
|
cliId,
|
|
1249
1403
|
baseURL: opts.cliConfig.baseURL,
|
|
1250
|
-
// Auto-broker prefers the brokered access-token mode when the descriptor
|
|
1251
|
-
// advertises it: nothing credential-shaped is persisted, and the next
|
|
1252
|
-
// `skrr` process simply re-redeems over loopback. The legacy refresh-
|
|
1253
|
-
// family mint remains the fallback for v1 descriptors and the redeem
|
|
1254
|
-
// path for explicit `skrr login`.
|
|
1255
|
-
preferHandoffMode: 'access_token',
|
|
1256
1404
|
profile: opts.profile ?? (0, keychain_1.getActiveProfile)(),
|
|
1257
1405
|
...(opts.bootstrapPathOverride ? { bootstrapPathOverride: opts.bootstrapPathOverride } : {}),
|
|
1406
|
+
};
|
|
1407
|
+
const harness = (0, harness_context_1.isHarnessCliContext)();
|
|
1408
|
+
// OSK-13254 — a person's first `skrr` on a machine whose daemon is signed in
|
|
1409
|
+
// ADOPTS a CLI session of its own: a separate cli-scope family, minted by the
|
|
1410
|
+
// daemon's hand-off and stored like a browser login, so later commands do
|
|
1411
|
+
// not depend on the daemon being up. Everything else borrows a short-lived
|
|
1412
|
+
// token for this process, which is what the automatic path always did
|
|
1413
|
+
// (OSK-12018): a process the daemon started for an agent or tool, a 401
|
|
1414
|
+
// recovery, a profile signed out with `skrr logout`, and the opt-out.
|
|
1415
|
+
if (opts.adopt && !cliSessionAdoptionBlocker(opts.cliConfig)) {
|
|
1416
|
+
const adopted = await adoptCliSessionThroughDaemon(base, opts.adoptionLockRetries ?? 40);
|
|
1417
|
+
if (adopted.adoptedBySibling)
|
|
1418
|
+
return { triggered: true, adoptedBySibling: true };
|
|
1419
|
+
const outcome = adopted.outcome;
|
|
1420
|
+
return {
|
|
1421
|
+
triggered: true,
|
|
1422
|
+
outcome,
|
|
1423
|
+
...(updatedConfig && outcome.ok ? { updatedConfig } : {}),
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
const outcome = await attemptDaemonBrokerLoginAndPersist({
|
|
1427
|
+
...base,
|
|
1428
|
+
// Borrow: the access-token mode, held in memory for this process; the next
|
|
1429
|
+
// `skrr` re-redeems over loopback. A v1 descriptor still takes the durable
|
|
1430
|
+
// mint here (the compatibility window in `brokerThrough`), except for a
|
|
1431
|
+
// harness-context process, which never stores a sign-in.
|
|
1432
|
+
preferHandoffMode: 'access_token',
|
|
1433
|
+
...(harness ? { borrowOnly: true } : {}),
|
|
1258
1434
|
});
|
|
1259
1435
|
return {
|
|
1260
1436
|
triggered: true,
|
|
1261
1437
|
outcome,
|
|
1262
|
-
...(updatedConfig && outcome.ok ? { updatedConfig } : {}),
|
|
1438
|
+
...(updatedConfig && outcome.ok && !harness ? { updatedConfig } : {}),
|
|
1263
1439
|
};
|
|
1264
1440
|
}
|
|
@@ -96,3 +96,17 @@ export declare function describeGuestDaemonUnavailable(failure: DaemonBrokerFail
|
|
|
96
96
|
* nothing waited for): "Not signed in" is then the true sentence.
|
|
97
97
|
*/
|
|
98
98
|
export declare function describeLocalDaemonUnavailable(failure: DaemonBrokerFailure, bin?: string): BrokerRefusalDescription | null;
|
|
99
|
+
/**
|
|
100
|
+
* The one description of a laptop or guest broker failure for every surface
|
|
101
|
+
* that has no separate guest probe of its own: a daemon that answered and
|
|
102
|
+
* refused (`describeBrokerRefusal`), else a daemon whose descriptor is on disk
|
|
103
|
+
* but which did not answer — down, crashed or mid-restart
|
|
104
|
+
* (`describeLocalDaemonUnavailable`).
|
|
105
|
+
*
|
|
106
|
+
* `requireAuth` composed these two by hand; `skrr login`'s progress line and
|
|
107
|
+
* headless refusal, and the mid-command 401 paths, consulted only the first,
|
|
108
|
+
* so a daemon that was restarting read there as "no daemon at all" — the
|
|
109
|
+
* generic browser refusal, or a bare "Run `skrr login`" (OSK-13099). Returns
|
|
110
|
+
* null only when there is genuinely nothing to say about a daemon.
|
|
111
|
+
*/
|
|
112
|
+
export declare function describeBrokerFailure(failure: DaemonBrokerFailure, bin?: string): BrokerRefusalDescription | null;
|
|
@@ -4,6 +4,7 @@ exports.isAnsweredBrokerRefusal = isAnsweredBrokerRefusal;
|
|
|
4
4
|
exports.describeBrokerRefusal = describeBrokerRefusal;
|
|
5
5
|
exports.describeGuestDaemonUnavailable = describeGuestDaemonUnavailable;
|
|
6
6
|
exports.describeLocalDaemonUnavailable = describeLocalDaemonUnavailable;
|
|
7
|
+
exports.describeBrokerFailure = describeBrokerFailure;
|
|
7
8
|
/**
|
|
8
9
|
* True when a daemon ANSWERED the hand-off and refused it — as opposed to no
|
|
9
10
|
* daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
|
|
@@ -261,7 +262,7 @@ function describeLocalDaemonUnavailable(failure, bin = 'skrr') {
|
|
|
261
262
|
if (!headline)
|
|
262
263
|
return null;
|
|
263
264
|
const remedy = 'You are not signed out: your sign-in is held by that service, which normally restarts on its own. ' +
|
|
264
|
-
`Retry in a
|
|
265
|
+
`Retry in a few seconds, or start it with \`${bin} daemon start\` if it stays down. \`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side, ` +
|
|
265
266
|
`and \`${bin} login\` signs this CLI in without it.`;
|
|
266
267
|
return {
|
|
267
268
|
headline: `${headline}${waited}`,
|
|
@@ -289,3 +290,19 @@ function describeLocalDaemonUnavailable(failure, bin = 'skrr') {
|
|
|
289
290
|
},
|
|
290
291
|
};
|
|
291
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* The one description of a laptop or guest broker failure for every surface
|
|
295
|
+
* that has no separate guest probe of its own: a daemon that answered and
|
|
296
|
+
* refused (`describeBrokerRefusal`), else a daemon whose descriptor is on disk
|
|
297
|
+
* but which did not answer — down, crashed or mid-restart
|
|
298
|
+
* (`describeLocalDaemonUnavailable`).
|
|
299
|
+
*
|
|
300
|
+
* `requireAuth` composed these two by hand; `skrr login`'s progress line and
|
|
301
|
+
* headless refusal, and the mid-command 401 paths, consulted only the first,
|
|
302
|
+
* so a daemon that was restarting read there as "no daemon at all" — the
|
|
303
|
+
* generic browser refusal, or a bare "Run `skrr login`" (OSK-13099). Returns
|
|
304
|
+
* null only when there is genuinely nothing to say about a daemon.
|
|
305
|
+
*/
|
|
306
|
+
function describeBrokerFailure(failure, bin = 'skrr') {
|
|
307
|
+
return describeBrokerRefusal(failure, bin) ?? describeLocalDaemonUnavailable(failure, bin);
|
|
308
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function isHarnessCliContext(env?: Readonly<Record<string, string | undefined>>): boolean;
|
|
2
|
+
/** Thrown by `writeToBackend` when a harness-context process tries to persist. */
|
|
3
|
+
export declare class HarnessCredentialPersistRefused extends Error {
|
|
4
|
+
readonly code = "HARNESS_CONTEXT_PERSIST_REFUSED";
|
|
5
|
+
constructor();
|
|
6
|
+
}
|
|
7
|
+
/** The commands a harness-context process may not run (they own the stored session). */
|
|
8
|
+
export declare const HARNESS_REFUSED_AUTH_COMMANDS: ReadonlySet<string>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HARNESS_REFUSED_AUTH_COMMANDS = exports.HarnessCredentialPersistRefused = void 0;
|
|
4
|
+
exports.isHarnessCliContext = isHarnessCliContext;
|
|
5
|
+
/**
|
|
6
|
+
* harness-context.ts — is this `skrr` running for an agent the daemon started?
|
|
7
|
+
* (OSK-13254)
|
|
8
|
+
*
|
|
9
|
+
* A person's `skrr` may adopt a CLI refresh session of its own through the
|
|
10
|
+
* local daemon's hand-off, so that signing in survives the daemon restarting
|
|
11
|
+
* (`daemonBroker.ts`, `maybeAutoBroker`). A `skrr` the daemon spawned for an
|
|
12
|
+
* agent or a tool must not: it borrows short-lived tokens from the daemon for
|
|
13
|
+
* each process, and never reads, writes, rotates or revokes the person's
|
|
14
|
+
* stored CLI session.
|
|
15
|
+
*
|
|
16
|
+
* Two daemon-set markers say so, and either is enough:
|
|
17
|
+
*
|
|
18
|
+
* - `OVERSKY_AUTONOMOUS_SESSION=1` — a harness session (`session-wrapper.ts`,
|
|
19
|
+
* `api-local-session-client.ts`). Such a process already never reaches the
|
|
20
|
+
* stored-credential chain (`BaseCommand.init` returns early on it); it is
|
|
21
|
+
* listed here so every guard below answers the same question.
|
|
22
|
+
* - `SKRR_DAEMON_CHILD` — every other agent/tool child: one-shot Bash,
|
|
23
|
+
* background Bash, agent terminals, harness backends
|
|
24
|
+
* (`daemon/src/env-sanitize.ts`, `agent-terminal-env.ts`). It survives the
|
|
25
|
+
* shared sanitizer, so grandchildren keep it.
|
|
26
|
+
*
|
|
27
|
+
* What this is and is not, stated so nobody leans on it harder than it holds:
|
|
28
|
+
* on a laptop the daemon, the agent and the person share one uid, so this is
|
|
29
|
+
* POLICY — a child that deletes the marker can do what the person's shell can.
|
|
30
|
+
* Where a uid boundary exists (Dedicated Runtime, identity-split Hosted
|
|
31
|
+
* Machines) the guest descriptor offers only the access-token mode, so a
|
|
32
|
+
* durable family cannot be minted there whatever the environment says. This
|
|
33
|
+
* is the same scope invariant I1 has today
|
|
34
|
+
* (`docs/architecture/managed-inference-execution-shapes-2026-08-12.md` §15).
|
|
35
|
+
*/
|
|
36
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
37
|
+
function isHarnessCliContext(env = process.env) {
|
|
38
|
+
return env.OVERSKY_AUTONOMOUS_SESSION === '1' || (0, auth_core_1.isDaemonChildEnv)(env);
|
|
39
|
+
}
|
|
40
|
+
/** Thrown by `writeToBackend` when a harness-context process tries to persist. */
|
|
41
|
+
class HarnessCredentialPersistRefused extends Error {
|
|
42
|
+
code = 'HARNESS_CONTEXT_PERSIST_REFUSED';
|
|
43
|
+
constructor() {
|
|
44
|
+
super('Refusing to store a skrr sign-in from a process the skrr background service started for an ' +
|
|
45
|
+
'agent or tool. skrr here borrows short-lived tokens from the service; sign in from your own terminal.');
|
|
46
|
+
this.name = 'HarnessCredentialPersistRefused';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.HarnessCredentialPersistRefused = HarnessCredentialPersistRefused;
|
|
50
|
+
/** The commands a harness-context process may not run (they own the stored session). */
|
|
51
|
+
exports.HARNESS_REFUSED_AUTH_COMMANDS = new Set([
|
|
52
|
+
'login',
|
|
53
|
+
'logout',
|
|
54
|
+
'pair',
|
|
55
|
+
]);
|
package/dist/lib/login.js
CHANGED
|
@@ -202,7 +202,10 @@ async function cliLogin(opts = {}) {
|
|
|
202
202
|
// The daemon answered and refused. Say so before whatever comes next —
|
|
203
203
|
// a browser that opens with no stated cause, or a refusal about terminals
|
|
204
204
|
// that says nothing about the daemon, both hid the real reason.
|
|
205
|
-
|
|
205
|
+
// A daemon whose descriptor is on disk but which did not answer (down or
|
|
206
|
+
// mid-restart) is described too: without it a headless `skrr login` during
|
|
207
|
+
// a restart said only that this shell has no terminal (OSK-13099).
|
|
208
|
+
brokerRefusal = (0, daemonBrokerRefusal_1.describeBrokerFailure)(brokerResult);
|
|
206
209
|
if (brokerRefusal && !(0, ssh_detect_1.isNonInteractive)()) {
|
|
207
210
|
console.log('');
|
|
208
211
|
console.log(` Asked the local skrr service for a session first. ${brokerRefusal.headline}`);
|
package/dist/lib/refresh.js
CHANGED
|
@@ -30,6 +30,7 @@ const config_1 = require("./config");
|
|
|
30
30
|
const cli_id_1 = require("./cli-id");
|
|
31
31
|
const credential_resolver_1 = require("./credential-resolver");
|
|
32
32
|
const device_identity_1 = require("./device-identity");
|
|
33
|
+
const harness_context_1 = require("./harness-context");
|
|
33
34
|
exports.CLI_REFRESH_EARLY_MS = 5 * 60 * 1000;
|
|
34
35
|
/**
|
|
35
36
|
* Stable origin header so the server's benign-grace matcher can identify
|
|
@@ -96,6 +97,9 @@ function canMutatePersistedCredential(credential, bareMode) {
|
|
|
96
97
|
return false;
|
|
97
98
|
if ((0, auth_core_1.isStaticTokenKind)(credential.kind))
|
|
98
99
|
return false;
|
|
100
|
+
// A borrower never rotates the person's stored family (OSK-13254).
|
|
101
|
+
if ((0, harness_context_1.isHarnessCliContext)())
|
|
102
|
+
return false;
|
|
99
103
|
// These sources are operator-owned or generated on demand. Never rotate
|
|
100
104
|
// them into the local keychain/file store behind the operator's back.
|
|
101
105
|
return credential.source === 'keychain' || credential.source === 'file';
|
|
@@ -35,7 +35,7 @@ export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest,
|
|
|
35
35
|
export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
|
|
36
36
|
export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
|
|
37
37
|
export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, harnessLoadsClaudeSkillsLayout, } from './harnessSkillLayout.js';
|
|
38
|
-
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
|
|
38
|
+
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, DAEMON_CHILD_ENV, isDaemonChildEnv, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
|
|
39
39
|
export { loopbackFetch, assertLoopbackHttpUrl, isLoopbackHostname, isLoopbackAddress, LoopbackTargetRefusedError, LOOPBACK_DEFAULT_MAX_BODY_BYTES, type LoopbackFetch, type LoopbackFetchInit, } from './loopbackHttp.js';
|
|
40
40
|
export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
|
|
41
41
|
export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
|
|
@@ -25,8 +25,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.isStaticTokenKind = exports.PERSONAL_ACCESS_TOKEN_PREFIX = exports.CI_TOKEN_PREFIX = exports.classifyTokenKind = exports.invalidateAuthHelperCache = exports.__resetCredentialResolverForTest = exports.CREDENTIAL_PRECEDENCE = exports.CredentialResolver = exports.__setFdReaderForTest = exports.readAuthFromFd = exports.createRefreshScheduler = exports.UNKNOWN_401_BUDGET = exports.__resetUnknown401StreaksForTest = exports.__refreshDiagnostics = exports.installLockExitHandlers = exports.clearReauthState = exports.needsReauthState = exports.persistReauthState = exports.withAuthLock = exports.revokeDaemonRefreshSession = exports.refreshDaemonToken = exports.classifyRefreshResponse = exports.EXIT_NEEDS_REAUTH = exports.reauthCommand = exports.formatReauthMessage = exports.handleMidStreamAuthExpiry = exports.isAuthExpiredSseEvent = exports.decodeJwtPayload = exports.decodeJwtExpiry = exports.TransientAuthFailure = exports.PermanentAuthFailure = exports.emitAuthTelemetry = exports.getAuthBinaryName = exports.getAuthKillSwitches = exports.getAuthConfigDir = exports.getAuthLogger = exports.configureAuthCore = exports.OAUTH_CLI_SCOPE = exports.isDaemonScope = exports.VALID_DAEMON_SCOPE_SET = exports.ALL_DAEMON_SCOPES = exports.SCOPES = void 0;
|
|
26
26
|
exports.jwkThumbprint = exports.generateDeviceKeyPair = exports.buildDeviceProof = exports.zeroizeKekCaches = exports.WindowsKek = exports.registerZeroizeHook = exports.MacosKek = exports.LinuxKek = exports.KekUnavailableError = exports.UnavailableKek = exports.InMemoryKek = exports.getKekStrategy = exports.__setKekStrategyForTest = exports.credEnvelopeWrap = exports.credEnvelopeUnwrap = exports.credEnvelopeSerialize = exports.credSealBuffer = exports.credOpenBuffer = exports.CRED_KEY_LEN = exports.isCredEnvelopeString = exports.generateDek = exports.CRED_ENVELOPE_MAGIC = exports.credEnvelopeEncrypt = exports.credEnvelopeDeserialize = exports.credEnvelopeDecrypt = exports.buildCredentialAad = exports.isValidRecoveryCode = exports.normalizeRecoveryCode = exports.parseRecoveryCode = exports.formatRecoveryCode = exports.MAX_TTL_DAYS = exports.MIN_TTL_DAYS = exports.parseTtlDays = exports.LoginExchangeError = exports.LoginInitError = exports.loginWithLocalhost = exports.LocalCallbackMalformedError = exports.LocalCallbackDeniedError = exports.LocalCallbackStateMismatchError = exports.LocalCallbackTimeoutError = exports.LocalCallbackBindError = exports.LocalCallbackError = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.startLocalCallback = exports.verifyChallenge = exports.safeEqual = exports.generateState = exports.challenge = exports.generateVerifier = exports.invalidateHeadersHelperCache = void 0;
|
|
27
27
|
exports.ensureMachineDaemonId = exports.__resetMachineUuidCacheForTest = exports.HOSTED_RUNTIME_MACHINE_SOURCES = exports.hostedRuntimeIdOverride = exports.getMachineUuid = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceKeySplit = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.DEFAULT_UNREADABLE_CREDENTIAL_HINT = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.splitKeyConvergencePolicy = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDpopProof = exports.normalizeDpopHtu = exports.isJwkThumbprint = exports.isAth = exports.createDpopKey = exports.accessTokenHash = exports.DPOP_PROOF_VERIFY_UNAVAILABLE = exports.DPOP_PROOF_TYP = exports.DPOP_PROOF_REQUIRED = exports.DPOP_PROOF_INVALID = exports.DPOP_MAX_AGE_SECONDS = exports.DPOP_HEADER = exports.verifyDeviceProof = exports.tokenHash = exports.publicJwkFromX = exports.normalizeHtu = void 0;
|
|
28
|
-
exports.
|
|
29
|
-
exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = void 0;
|
|
28
|
+
exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES = exports.LoopbackTargetRefusedError = exports.isLoopbackAddress = exports.isLoopbackHostname = exports.assertLoopbackHttpUrl = exports.loopbackFetch = exports.withLoopbackNoProxy = exports.envHasProxy = exports.LOOPBACK_NO_PROXY_HOSTS = exports.isDaemonChildEnv = exports.DAEMON_CHILD_ENV = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.harnessLoadsClaudeSkillsLayout = exports.claudeSkillsLayoutEvidence = exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = exports.getHarnessTier = exports.HARNESS_TIERS = exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = void 0;
|
|
29
|
+
exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = exports.describeLegacyState = exports.findLegacyLocalState = void 0;
|
|
30
30
|
// Daemon scope vocabulary — single source of truth for non-Mongoose tiers
|
|
31
31
|
// (daemon, desktop, cli). Schema-bound canonical lives in
|
|
32
32
|
// `@skrr-ai/data-schemas/common/daemonScopes`; the two are kept byte-equal
|
|
@@ -346,6 +346,8 @@ Object.defineProperty(exports, "SENSITIVE_ENV_PREFIXES", { enumerable: true, get
|
|
|
346
346
|
Object.defineProperty(exports, "SENSITIVE_ENV_SUFFIXES", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_SUFFIXES; } });
|
|
347
347
|
Object.defineProperty(exports, "SENSITIVE_ENV_NAME_TOKENS", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_NAME_TOKENS; } });
|
|
348
348
|
Object.defineProperty(exports, "SENSITIVE_ENV_PREFIX_EXCEPTIONS", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_PREFIX_EXCEPTIONS; } });
|
|
349
|
+
Object.defineProperty(exports, "DAEMON_CHILD_ENV", { enumerable: true, get: function () { return spawnEnv_js_1.DAEMON_CHILD_ENV; } });
|
|
350
|
+
Object.defineProperty(exports, "isDaemonChildEnv", { enumerable: true, get: function () { return spawnEnv_js_1.isDaemonChildEnv; } });
|
|
349
351
|
Object.defineProperty(exports, "LOOPBACK_NO_PROXY_HOSTS", { enumerable: true, get: function () { return spawnEnv_js_1.LOOPBACK_NO_PROXY_HOSTS; } });
|
|
350
352
|
Object.defineProperty(exports, "envHasProxy", { enumerable: true, get: function () { return spawnEnv_js_1.envHasProxy; } });
|
|
351
353
|
Object.defineProperty(exports, "withLoopbackNoProxy", { enumerable: true, get: function () { return spawnEnv_js_1.withLoopbackNoProxy; } });
|
|
@@ -77,6 +77,31 @@ export declare const SENSITIVE_ENV_SUFFIXES: readonly string[];
|
|
|
77
77
|
export declare const SENSITIVE_ENV_NAME_TOKENS: readonly string[];
|
|
78
78
|
/** Specific names to keep — the prefix sweeps are broad on purpose. */
|
|
79
79
|
export declare const SENSITIVE_ENV_PREFIX_EXCEPTIONS: ReadonlySet<string>;
|
|
80
|
+
/**
|
|
81
|
+
* OSK-13254 — the marker the daemon stamps on every agent- or tool-driven child
|
|
82
|
+
* (`daemon/src/env-sanitize.ts`, `daemon/src/terminal/agent-terminal-env.ts`).
|
|
83
|
+
*
|
|
84
|
+
* A `skrr` that sees it is a borrower: it redeems short-lived tokens from the
|
|
85
|
+
* daemon's loopback broker, never adopts a CLI refresh session of its own, and
|
|
86
|
+
* never reads the person's stored one (`cli/src/lib/harness-context.ts`). A
|
|
87
|
+
* person's own terminal — including the web terminal, which is a human surface
|
|
88
|
+
* (`humanTerminalEnv`) — does not carry it.
|
|
89
|
+
*
|
|
90
|
+
* The marker can only take authority away, so a process that forges it hurts
|
|
91
|
+
* nobody but itself. On a laptop it is POLICY, not a boundary: a same-uid child
|
|
92
|
+
* that deletes it can do whatever the person's own shell can. Where a uid
|
|
93
|
+
* boundary exists (Dedicated Runtime, identity-split Hosted Machines), the
|
|
94
|
+
* guest descriptor already offers only the access-token mode, so a durable CLI
|
|
95
|
+
* family cannot be minted there whatever the environment says.
|
|
96
|
+
*/
|
|
97
|
+
export declare const DAEMON_CHILD_ENV = "SKRR_DAEMON_CHILD";
|
|
98
|
+
/**
|
|
99
|
+
* True when `env` carries the daemon-child marker with ANY non-empty value.
|
|
100
|
+
* Deliberately no "0 means off": the marker only narrows authority, so the
|
|
101
|
+
* one value that could switch it off is the one a request-carried environment
|
|
102
|
+
* would forge.
|
|
103
|
+
*/
|
|
104
|
+
export declare function isDaemonChildEnv(env: Readonly<Record<string, string | undefined>>): boolean;
|
|
80
105
|
/**
|
|
81
106
|
* Build a sanitized snapshot of the parent's own env for a spawned
|
|
82
107
|
* child, stripping credentials and library-injection vectors while
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
* serve that case: a deny-list with a hole is not a deny-list.
|
|
41
41
|
*/
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
-
exports.LOOPBACK_NO_PROXY_HOSTS = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = void 0;
|
|
43
|
+
exports.LOOPBACK_NO_PROXY_HOSTS = exports.DAEMON_CHILD_ENV = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = void 0;
|
|
44
|
+
exports.isDaemonChildEnv = isDaemonChildEnv;
|
|
44
45
|
exports.sanitizeSpawnEnv = sanitizeSpawnEnv;
|
|
45
46
|
exports.envHasProxy = envHasProxy;
|
|
46
47
|
exports.withLoopbackNoProxy = withLoopbackNoProxy;
|
|
@@ -154,7 +155,40 @@ exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = new Set([
|
|
|
154
155
|
// engine) must keep it to detect they already run as the workload uid. Not
|
|
155
156
|
// a secret — it authorizes nothing without the matching real uid.
|
|
156
157
|
'SKRR_WORKLOAD_UID',
|
|
158
|
+
// OSK-13254 — "this process descends from a skrrd agent/tool spawn". It only
|
|
159
|
+
// ever REMOVES authority (a `skrr` that sees it borrows from the daemon and
|
|
160
|
+
// never reads or writes a stored CLI session), so it must survive every later
|
|
161
|
+
// re-sanitization: a Bash tool that runs `skrr code`, whose engine runs `skrr`,
|
|
162
|
+
// is still the agent's process. See `DAEMON_CHILD_ENV` below.
|
|
163
|
+
'SKRR_DAEMON_CHILD',
|
|
157
164
|
]);
|
|
165
|
+
/**
|
|
166
|
+
* OSK-13254 — the marker the daemon stamps on every agent- or tool-driven child
|
|
167
|
+
* (`daemon/src/env-sanitize.ts`, `daemon/src/terminal/agent-terminal-env.ts`).
|
|
168
|
+
*
|
|
169
|
+
* A `skrr` that sees it is a borrower: it redeems short-lived tokens from the
|
|
170
|
+
* daemon's loopback broker, never adopts a CLI refresh session of its own, and
|
|
171
|
+
* never reads the person's stored one (`cli/src/lib/harness-context.ts`). A
|
|
172
|
+
* person's own terminal — including the web terminal, which is a human surface
|
|
173
|
+
* (`humanTerminalEnv`) — does not carry it.
|
|
174
|
+
*
|
|
175
|
+
* The marker can only take authority away, so a process that forges it hurts
|
|
176
|
+
* nobody but itself. On a laptop it is POLICY, not a boundary: a same-uid child
|
|
177
|
+
* that deletes it can do whatever the person's own shell can. Where a uid
|
|
178
|
+
* boundary exists (Dedicated Runtime, identity-split Hosted Machines), the
|
|
179
|
+
* guest descriptor already offers only the access-token mode, so a durable CLI
|
|
180
|
+
* family cannot be minted there whatever the environment says.
|
|
181
|
+
*/
|
|
182
|
+
exports.DAEMON_CHILD_ENV = 'SKRR_DAEMON_CHILD';
|
|
183
|
+
/**
|
|
184
|
+
* True when `env` carries the daemon-child marker with ANY non-empty value.
|
|
185
|
+
* Deliberately no "0 means off": the marker only narrows authority, so the
|
|
186
|
+
* one value that could switch it off is the one a request-carried environment
|
|
187
|
+
* would forge.
|
|
188
|
+
*/
|
|
189
|
+
function isDaemonChildEnv(env) {
|
|
190
|
+
return Boolean(env[exports.DAEMON_CHILD_ENV]?.trim());
|
|
191
|
+
}
|
|
158
192
|
/**
|
|
159
193
|
* Build a sanitized snapshot of the parent's own env for a spawned
|
|
160
194
|
* child, stripping credentials and library-injection vectors while
|
|
@@ -35,7 +35,7 @@ export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest,
|
|
|
35
35
|
export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
|
|
36
36
|
export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
|
|
37
37
|
export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, harnessLoadsClaudeSkillsLayout, } from './harnessSkillLayout.js';
|
|
38
|
-
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
|
|
38
|
+
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, DAEMON_CHILD_ENV, isDaemonChildEnv, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
|
|
39
39
|
export { loopbackFetch, assertLoopbackHttpUrl, isLoopbackHostname, isLoopbackAddress, LoopbackTargetRefusedError, LOOPBACK_DEFAULT_MAX_BODY_BYTES, type LoopbackFetch, type LoopbackFetchInit, } from './loopbackHttp.js';
|
|
40
40
|
export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
|
|
41
41
|
export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
|
|
@@ -149,7 +149,7 @@ export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, har
|
|
|
149
149
|
// spawns the SAME engine and did not). `daemon/src/env-sanitize.ts` re-exports
|
|
150
150
|
// these so its own importers are unchanged, and keeps `filterCustomEnv` — that
|
|
151
151
|
// governs SERVER-PROVIDED env, which no other process is handed.
|
|
152
|
-
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
|
|
152
|
+
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, DAEMON_CHILD_ENV, isDaemonChildEnv, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
|
|
153
153
|
// OSK-12134 — the one proxy-free client for loopback HTTP. Bun's fetch (and
|
|
154
154
|
// Node's under NODE_USE_ENV_PROXY) sends 127.0.0.1 requests to HTTP_PROXY,
|
|
155
155
|
// credentials and all; every loopback caller goes through this instead.
|
|
@@ -77,6 +77,31 @@ export declare const SENSITIVE_ENV_SUFFIXES: readonly string[];
|
|
|
77
77
|
export declare const SENSITIVE_ENV_NAME_TOKENS: readonly string[];
|
|
78
78
|
/** Specific names to keep — the prefix sweeps are broad on purpose. */
|
|
79
79
|
export declare const SENSITIVE_ENV_PREFIX_EXCEPTIONS: ReadonlySet<string>;
|
|
80
|
+
/**
|
|
81
|
+
* OSK-13254 — the marker the daemon stamps on every agent- or tool-driven child
|
|
82
|
+
* (`daemon/src/env-sanitize.ts`, `daemon/src/terminal/agent-terminal-env.ts`).
|
|
83
|
+
*
|
|
84
|
+
* A `skrr` that sees it is a borrower: it redeems short-lived tokens from the
|
|
85
|
+
* daemon's loopback broker, never adopts a CLI refresh session of its own, and
|
|
86
|
+
* never reads the person's stored one (`cli/src/lib/harness-context.ts`). A
|
|
87
|
+
* person's own terminal — including the web terminal, which is a human surface
|
|
88
|
+
* (`humanTerminalEnv`) — does not carry it.
|
|
89
|
+
*
|
|
90
|
+
* The marker can only take authority away, so a process that forges it hurts
|
|
91
|
+
* nobody but itself. On a laptop it is POLICY, not a boundary: a same-uid child
|
|
92
|
+
* that deletes it can do whatever the person's own shell can. Where a uid
|
|
93
|
+
* boundary exists (Dedicated Runtime, identity-split Hosted Machines), the
|
|
94
|
+
* guest descriptor already offers only the access-token mode, so a durable CLI
|
|
95
|
+
* family cannot be minted there whatever the environment says.
|
|
96
|
+
*/
|
|
97
|
+
export declare const DAEMON_CHILD_ENV = "SKRR_DAEMON_CHILD";
|
|
98
|
+
/**
|
|
99
|
+
* True when `env` carries the daemon-child marker with ANY non-empty value.
|
|
100
|
+
* Deliberately no "0 means off": the marker only narrows authority, so the
|
|
101
|
+
* one value that could switch it off is the one a request-carried environment
|
|
102
|
+
* would forge.
|
|
103
|
+
*/
|
|
104
|
+
export declare function isDaemonChildEnv(env: Readonly<Record<string, string | undefined>>): boolean;
|
|
80
105
|
/**
|
|
81
106
|
* Build a sanitized snapshot of the parent's own env for a spawned
|
|
82
107
|
* child, stripping credentials and library-injection vectors while
|