@skrr-ai/cli 0.1.49 → 0.1.51
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/README.md +5 -4
- package/dist/base-command.js +75 -6
- package/dist/commands/agents/chat.d.ts +14 -0
- package/dist/commands/agents/chat.js +37 -1
- package/dist/commands/agents/create.js +14 -7
- package/dist/commands/agents/show.d.ts +9 -0
- package/dist/commands/agents/show.js +53 -0
- package/dist/commands/agents/update.d.ts +9 -0
- package/dist/commands/agents/update.js +38 -5
- package/dist/commands/balance/show.d.ts +34 -0
- package/dist/commands/balance/show.js +92 -2
- package/dist/commands/code/index.d.ts +1 -0
- package/dist/commands/code/index.js +49 -5
- package/dist/commands/code/run.js +5 -1
- package/dist/commands/commitments/cycles.d.ts +19 -0
- package/dist/commands/commitments/cycles.js +46 -0
- package/dist/commands/commitments/doctor.d.ts +1 -0
- package/dist/commands/commitments/doctor.js +19 -4
- package/dist/commands/commitments/effective-policy.js +40 -1
- package/dist/commands/commitments/explain.d.ts +17 -0
- package/dist/commands/commitments/explain.js +40 -0
- package/dist/commands/commitments/next-tick.js +3 -1
- package/dist/commands/commitments/preflight.js +10 -0
- package/dist/commands/commitments/show.js +11 -1
- package/dist/commands/daemon/config.d.ts +15 -0
- package/dist/commands/daemon/config.js +38 -0
- package/dist/commands/daemon/index.js +1 -0
- package/dist/commands/daemon/status.js +15 -1
- package/dist/commands/goals/create.d.ts +1 -0
- package/dist/commands/goals/create.js +22 -0
- package/dist/commands/harnesses/models.d.ts +54 -0
- package/dist/commands/harnesses/models.js +100 -7
- package/dist/commands/harnesses/show.d.ts +11 -0
- package/dist/commands/harnesses/show.js +35 -0
- package/dist/commands/harnesses/update.d.ts +28 -1
- package/dist/commands/harnesses/update.js +73 -10
- package/dist/commands/logout.js +12 -1
- package/dist/commands/machines/dedicated/index.js +1 -1
- package/dist/commands/machines/dedicated/sign-in.js +4 -1
- package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
- package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
- package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
- package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
- package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
- package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
- package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
- package/dist/commands/machines/dedicated/terminal.js +27 -2
- package/dist/commands/machines/hosted/connect.js +2 -2
- package/dist/commands/machines/hosted/destroy.js +1 -1
- package/dist/commands/machines/hosted/exec.js +1 -1
- package/dist/commands/machines/hosted/list.js +1 -1
- package/dist/commands/machines/hosted/pause.js +1 -1
- package/dist/commands/machines/hosted/pull.js +1 -1
- package/dist/commands/machines/hosted/resume.js +1 -1
- package/dist/commands/machines/hosted/start.js +2 -2
- package/dist/commands/machines/hosted/status.js +2 -1
- package/dist/commands/outbox/list.d.ts +12 -1
- package/dist/commands/outbox/list.js +31 -2
- package/dist/commands/revoke-daemon.js +9 -0
- package/dist/commands/spaces/create.d.ts +1 -0
- package/dist/commands/spaces/create.js +17 -0
- package/dist/commands/spaces/show.js +5 -0
- package/dist/commands/spaces/update.d.ts +1 -0
- package/dist/commands/spaces/update.js +16 -0
- package/dist/commands/tasks/create.d.ts +1 -0
- package/dist/commands/tasks/create.js +6 -0
- package/dist/commands/tasks/list.js +1 -0
- package/dist/commands/tasks/show.js +45 -0
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +22 -0
- package/dist/commands/tasks/upsert.d.ts +1 -0
- package/dist/commands/tasks/upsert.js +16 -0
- package/dist/lib/agent-config.d.ts +2 -0
- package/dist/lib/agent-config.js +3 -1
- package/dist/lib/agentic-stream.d.ts +68 -1
- package/dist/lib/agentic-stream.js +294 -7
- package/dist/lib/api-fetch.js +19 -0
- package/dist/lib/auth-core-init.d.ts +2 -10
- package/dist/lib/auth-core-init.js +20 -67
- package/dist/lib/auth-failure.d.ts +33 -0
- package/dist/lib/auth-failure.js +65 -0
- package/dist/lib/auth-storage.d.ts +14 -0
- package/dist/lib/auth-storage.js +14 -0
- package/dist/lib/commitments.d.ts +37 -2
- package/dist/lib/commitments.js +148 -0
- package/dist/lib/daemonBroker.d.ts +134 -31
- package/dist/lib/daemonBroker.js +376 -31
- package/dist/lib/dedicated-lease-command.d.ts +14 -0
- package/dist/lib/dedicated-lease-command.js +30 -1
- package/dist/lib/dedicated-machines.js +8 -25
- package/dist/lib/dedicated-service-command.d.ts +11 -3
- package/dist/lib/dedicated-service-command.js +20 -3
- package/dist/lib/dedicated-service.d.ts +46 -0
- package/dist/lib/dedicated-service.js +85 -7
- package/dist/lib/dedicated-terminal.d.ts +21 -0
- package/dist/lib/dedicated-terminal.js +104 -9
- package/dist/lib/delegated-cli.js +4 -1
- package/dist/lib/exec-runtime-binary.d.ts +3 -1
- package/dist/lib/exec-runtime-binary.js +4 -2
- package/dist/lib/first-party-harness-broker.d.ts +10 -0
- package/dist/lib/first-party-harness-broker.js +13 -1
- package/dist/lib/first-party-harness-doctor.js +41 -1
- package/dist/lib/first-party-harness-managed.d.ts +4 -3
- package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
- package/dist/lib/first-party-harness-project-trust.js +364 -0
- package/dist/lib/first-party-harness.d.ts +9 -2
- package/dist/lib/first-party-harness.js +6 -5
- package/dist/lib/harnesses.d.ts +10 -0
- package/dist/lib/hosted-machines.d.ts +10 -1
- package/dist/lib/hosted-machines.js +36 -2
- package/dist/lib/live-runtime-binary.d.ts +40 -0
- package/dist/lib/live-runtime-binary.js +193 -0
- package/dist/lib/login.d.ts +23 -0
- package/dist/lib/login.js +60 -10
- package/dist/lib/machine-spend-cap.d.ts +23 -0
- package/dist/lib/machine-spend-cap.js +68 -0
- package/dist/lib/message-intent.js +3 -1
- package/dist/lib/node-adapter.js +15 -3
- package/dist/lib/tasks.d.ts +20 -0
- package/dist/lib/tasks.js +54 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +133 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +189 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +16 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +133 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +182 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +16 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +61 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3418 -3344
- package/oclif.manifest.json +24813 -24242
- package/package.json +4 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentialSession.ts — the shared credential-lifecycle kernel (policy half).
|
|
3
|
+
*
|
|
4
|
+
* A credential lifecycle is policy × mechanism. The policy — serve a cached
|
|
5
|
+
* token until its expiry-skew window, collapse concurrent acquires onto one
|
|
6
|
+
* in-flight operation, invalidate on a resource-side 401 — is identical for
|
|
7
|
+
* every surface and was being re-implemented per consumer (task token-broker,
|
|
8
|
+
* delegated-cli redemption, the human CLI refresh path). The mechanism —
|
|
9
|
+
* WHERE the durable secret lives and HOW a token is acquired — legitimately
|
|
10
|
+
* differs (in-memory daemon family vs file+envelope+lock vs loopback redeem)
|
|
11
|
+
* and stays behind the `acquire` seam.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately thin: no persistence, no retry policy, no logging. Callers
|
|
14
|
+
* own their store and their error surfaces; this owns the cache/single-
|
|
15
|
+
* flight/invalidate invariants so they cannot drift between surfaces.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_SKEW_MS = 30_000;
|
|
18
|
+
export function createCredentialSession(opts) {
|
|
19
|
+
const skewMs = opts.skewMs ?? DEFAULT_SKEW_MS;
|
|
20
|
+
const now = opts.now ?? Date.now;
|
|
21
|
+
let cached = null;
|
|
22
|
+
let inFlight = null;
|
|
23
|
+
let inFlightForced = false;
|
|
24
|
+
// Monotonic generation so a superseded in-flight acquire (a forceRefresh
|
|
25
|
+
// launched while a plain acquire was still running) cannot write its stale
|
|
26
|
+
// result back over the newer one.
|
|
27
|
+
let generation = 0;
|
|
28
|
+
const fresh = (t) => t.expiresAtMs == null || now() < t.expiresAtMs - skewMs;
|
|
29
|
+
const acquireShared = (force) => {
|
|
30
|
+
// A forced acquire must not join a non-forced in-flight one — that would
|
|
31
|
+
// serve the very credential a 401 just invalidated. A forced in-flight
|
|
32
|
+
// DOES satisfy non-forced waiters: it is already renewing.
|
|
33
|
+
if (inFlight && (inFlightForced || !force))
|
|
34
|
+
return inFlight;
|
|
35
|
+
const gen = ++generation;
|
|
36
|
+
const p = opts.acquire(force).then((t) => {
|
|
37
|
+
if (gen === generation)
|
|
38
|
+
cached = t;
|
|
39
|
+
return t;
|
|
40
|
+
});
|
|
41
|
+
inFlight = p;
|
|
42
|
+
inFlightForced = force;
|
|
43
|
+
// `then(cleanup, cleanup)` not `.finally(cleanup)`: finally propagates the
|
|
44
|
+
// rejection into a derived promise nobody awaits — an unhandledRejection.
|
|
45
|
+
const cleanup = () => {
|
|
46
|
+
if (inFlight === p) {
|
|
47
|
+
inFlight = null;
|
|
48
|
+
inFlightForced = false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
p.then(cleanup, cleanup);
|
|
52
|
+
return p;
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
async getToken(forceRefresh = false) {
|
|
56
|
+
if (forceRefresh)
|
|
57
|
+
cached = null;
|
|
58
|
+
if (cached && fresh(cached))
|
|
59
|
+
return cached.accessToken;
|
|
60
|
+
return (await acquireShared(forceRefresh)).accessToken;
|
|
61
|
+
},
|
|
62
|
+
invalidate() {
|
|
63
|
+
cached = null;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-attested tool approval (OSK-11975 follow-up to the `manual` mode
|
|
3
|
+
* refusal on `daemon:tool:request`).
|
|
4
|
+
*
|
|
5
|
+
* A daemon category in `manual` mode means "a PERSON approves each call". On
|
|
6
|
+
* the harness-session path the daemon asks through the permission bridge; on
|
|
7
|
+
* the daemon-tool path a cloud agent's call used to be refused outright,
|
|
8
|
+
* because the request carried no approval and there was no channel back to a
|
|
9
|
+
* person. The attested-approval protocol closes that gap without weakening
|
|
10
|
+
* the guarantee:
|
|
11
|
+
*
|
|
12
|
+
* 1. The daemon announces `daemon_tool_attested_approval_v1` and its
|
|
13
|
+
* resolved per-category posture (`toolPermissionModes`) at register time.
|
|
14
|
+
* 2. Before proxying a call, the server reads the announced posture for the
|
|
15
|
+
* tool's category. `manual` means it must ask first — through the same
|
|
16
|
+
* permission surface (`canUseTool` → `permission:request`) an in-process
|
|
17
|
+
* SDK turn would have used — and only then dispatch, carrying an
|
|
18
|
+
* `attestedApproval` object on the request.
|
|
19
|
+
* 3. The daemon verifies the attestation's SHAPE and BINDING — it is bound
|
|
20
|
+
* to this exact (toolUseId, toolName, toolInput digest) and expires
|
|
21
|
+
* quickly — and allows the call. A missing or malformed attestation gets
|
|
22
|
+
* the same named refusal as before.
|
|
23
|
+
*
|
|
24
|
+
* What the daemon verifies is the binding, not the server's honesty: the
|
|
25
|
+
* request already arrives over the daemon's authenticated server channel, so
|
|
26
|
+
* the field IS the attestation — it is unsigned, and `decidedBy` is a literal.
|
|
27
|
+
* What the binding buys is that the model — which authors `toolInput` —
|
|
28
|
+
* cannot mint an approval, and an approval for one call cannot be replayed
|
|
29
|
+
* onto another. The verifier also bounds the validity window itself (never
|
|
30
|
+
* trusting the issuer's `expiresAt` beyond `ATTESTED_TOOL_APPROVAL_TTL_MS`),
|
|
31
|
+
* and the daemon spends each approval once: a second delivery of the same
|
|
32
|
+
* attestation inside its window is refused as `replayed`. That ledger lives
|
|
33
|
+
* in daemon memory, so a daemon restart inside the (≤60s) window forgets it.
|
|
34
|
+
*/
|
|
35
|
+
/** Announced by a daemon that honours `attestedApproval` on daemon:tool:request. */
|
|
36
|
+
export declare const DAEMON_TOOL_ATTESTED_APPROVAL_CAPABILITY = "daemon_tool_attested_approval_v1";
|
|
37
|
+
/**
|
|
38
|
+
* Announced by a daemon that honours `toolPosture: 'read_only'` on
|
|
39
|
+
* daemon:tool:request — the daemon-tool-path expression of the wire `plan`
|
|
40
|
+
* session mode: reads run, every mutating category is refused with a named
|
|
41
|
+
* reason. Carries INTENT like `browserPolicy: 'read_only'` — the daemon owns
|
|
42
|
+
* what read-only means, so the server cannot widen the fence by shipping a
|
|
43
|
+
* wider value.
|
|
44
|
+
*/
|
|
45
|
+
export declare const DAEMON_TOOL_READ_ONLY_CAPABILITY = "daemon_tool_read_only_v1";
|
|
46
|
+
/**
|
|
47
|
+
* Tool categories the daemon recognizes for permission purposes. This is the
|
|
48
|
+
* wire vocabulary the daemon's announced `toolPermissionModes` is keyed on —
|
|
49
|
+
* shared so the server's pre-dispatch check and the daemon's own enforcement
|
|
50
|
+
* classify a tool name identically (a third map was exactly the drift this
|
|
51
|
+
* package exists to prevent).
|
|
52
|
+
*/
|
|
53
|
+
export type DaemonToolCategory = 'read' | 'write' | 'bash' | 'browser' | 'claude_session' | 'native_ui';
|
|
54
|
+
export declare const DAEMON_TOOL_CATEGORIES: readonly DaemonToolCategory[];
|
|
55
|
+
/** Map a tool name to its category. Unknown tools take the restrictive answer. */
|
|
56
|
+
export declare function categoryForDaemonTool(toolName: string): DaemonToolCategory;
|
|
57
|
+
/**
|
|
58
|
+
* The posture a daemon announces per category for the daemon-tool path:
|
|
59
|
+
* - `auto` — the call runs once guardrails pass; no attestation needed
|
|
60
|
+
* - `manual` — the call needs a person; the server must attach a fresh
|
|
61
|
+
* `attestedApproval` or the daemon refuses it
|
|
62
|
+
* - `delegated` — the category's own policy decides per action (browser,
|
|
63
|
+
* native UI); asking up front would ask the wrong question
|
|
64
|
+
*/
|
|
65
|
+
export type DaemonToolPathPosture = 'auto' | 'manual' | 'delegated';
|
|
66
|
+
export type DaemonToolPermissionModes = Readonly<Partial<Record<DaemonToolCategory, DaemonToolPathPosture>>>;
|
|
67
|
+
/** The wire field values `toolPosture` accepts. Unknown values must be ignored. */
|
|
68
|
+
export type DaemonToolPosture = 'read_only';
|
|
69
|
+
/**
|
|
70
|
+
* How long an attestation stays valid. Short: it answers ONE person, about
|
|
71
|
+
* THIS call, moments ago — it is not a standing grant. The server asks and
|
|
72
|
+
* dispatches back-to-back, so the window only has to outlive publish latency.
|
|
73
|
+
*/
|
|
74
|
+
export declare const ATTESTED_TOOL_APPROVAL_TTL_MS: number;
|
|
75
|
+
export interface AttestedToolApproval {
|
|
76
|
+
v: 1;
|
|
77
|
+
/** Binds the approval to this exact call — a different call gets nothing. */
|
|
78
|
+
toolUseId: string;
|
|
79
|
+
toolName: string;
|
|
80
|
+
/** SHA-256 of the canonical tool input (see `canonicalToolInputDigest`). */
|
|
81
|
+
inputSha256: string;
|
|
82
|
+
/** Who answered on the server's permission surface. */
|
|
83
|
+
decidedBy: 'user';
|
|
84
|
+
issuedAt: number;
|
|
85
|
+
expiresAt: number;
|
|
86
|
+
}
|
|
87
|
+
/** The digest both sides compute over `toolInput`. */
|
|
88
|
+
export declare function canonicalToolInputDigest(toolInput: unknown): string;
|
|
89
|
+
/** Server side: mint the attestation after a person approved THIS call. */
|
|
90
|
+
export declare function mintAttestedToolApproval(args: {
|
|
91
|
+
toolUseId: string;
|
|
92
|
+
toolName: string;
|
|
93
|
+
toolInput: unknown;
|
|
94
|
+
now?: number;
|
|
95
|
+
ttlMs?: number;
|
|
96
|
+
}): AttestedToolApproval;
|
|
97
|
+
/**
|
|
98
|
+
* Why an attestation did not approve a call — logged by the daemon, never
|
|
99
|
+
* shown to the caller beyond "absent" vs "did not verify".
|
|
100
|
+
* - `absent` — no attestation arrived (the server did not ask anyone)
|
|
101
|
+
* - `malformed` — not the v1 shape
|
|
102
|
+
* - `expired` — past `expiresAt`, or issued implausibly far in the future
|
|
103
|
+
* - `window` — the issuer claimed a validity window longer than
|
|
104
|
+
* `ATTESTED_TOOL_APPROVAL_TTL_MS` (or an empty/negative one)
|
|
105
|
+
* - `binding` — minted for a different call (toolUseId, tool, or input)
|
|
106
|
+
* - `replayed` — this exact approval was already spent on this daemon
|
|
107
|
+
* (the ledger is the verifier's caller's, see the daemon's
|
|
108
|
+
* `permissions/attestation-ledger.ts`)
|
|
109
|
+
*/
|
|
110
|
+
export type AttestedToolApprovalRefusalReason = 'absent' | 'malformed' | 'binding' | 'expired' | 'window' | 'replayed';
|
|
111
|
+
export type AttestedToolApprovalVerdict = {
|
|
112
|
+
ok: true;
|
|
113
|
+
} | {
|
|
114
|
+
ok: false;
|
|
115
|
+
reason: AttestedToolApprovalRefusalReason;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Clock skew tolerated on `issuedAt`: a server clock slightly ahead of the
|
|
119
|
+
* daemon's must not refuse a fresh approval.
|
|
120
|
+
*/
|
|
121
|
+
export declare const ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS: number;
|
|
122
|
+
/**
|
|
123
|
+
* Daemon side: verify an attestation against the call it claims to approve.
|
|
124
|
+
* Untrusted wire input — every field is checked, and any mismatch is a plain
|
|
125
|
+
* "no approval", never a partial credit. `reason` is for daemon logs; the
|
|
126
|
+
* refusal text stays generic so a probing caller learns nothing new.
|
|
127
|
+
*/
|
|
128
|
+
export declare function verifyAttestedToolApproval(approval: unknown, call: {
|
|
129
|
+
toolUseId: string;
|
|
130
|
+
toolName: string;
|
|
131
|
+
toolInput: unknown;
|
|
132
|
+
now?: number;
|
|
133
|
+
}): AttestedToolApprovalVerdict;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Server-attested tool approval (OSK-11975 follow-up to the `manual` mode
|
|
4
|
+
* refusal on `daemon:tool:request`).
|
|
5
|
+
*
|
|
6
|
+
* A daemon category in `manual` mode means "a PERSON approves each call". On
|
|
7
|
+
* the harness-session path the daemon asks through the permission bridge; on
|
|
8
|
+
* the daemon-tool path a cloud agent's call used to be refused outright,
|
|
9
|
+
* because the request carried no approval and there was no channel back to a
|
|
10
|
+
* person. The attested-approval protocol closes that gap without weakening
|
|
11
|
+
* the guarantee:
|
|
12
|
+
*
|
|
13
|
+
* 1. The daemon announces `daemon_tool_attested_approval_v1` and its
|
|
14
|
+
* resolved per-category posture (`toolPermissionModes`) at register time.
|
|
15
|
+
* 2. Before proxying a call, the server reads the announced posture for the
|
|
16
|
+
* tool's category. `manual` means it must ask first — through the same
|
|
17
|
+
* permission surface (`canUseTool` → `permission:request`) an in-process
|
|
18
|
+
* SDK turn would have used — and only then dispatch, carrying an
|
|
19
|
+
* `attestedApproval` object on the request.
|
|
20
|
+
* 3. The daemon verifies the attestation's SHAPE and BINDING — it is bound
|
|
21
|
+
* to this exact (toolUseId, toolName, toolInput digest) and expires
|
|
22
|
+
* quickly — and allows the call. A missing or malformed attestation gets
|
|
23
|
+
* the same named refusal as before.
|
|
24
|
+
*
|
|
25
|
+
* What the daemon verifies is the binding, not the server's honesty: the
|
|
26
|
+
* request already arrives over the daemon's authenticated server channel, so
|
|
27
|
+
* the field IS the attestation — it is unsigned, and `decidedBy` is a literal.
|
|
28
|
+
* What the binding buys is that the model — which authors `toolInput` —
|
|
29
|
+
* cannot mint an approval, and an approval for one call cannot be replayed
|
|
30
|
+
* onto another. The verifier also bounds the validity window itself (never
|
|
31
|
+
* trusting the issuer's `expiresAt` beyond `ATTESTED_TOOL_APPROVAL_TTL_MS`),
|
|
32
|
+
* and the daemon spends each approval once: a second delivery of the same
|
|
33
|
+
* attestation inside its window is refused as `replayed`. That ledger lives
|
|
34
|
+
* in daemon memory, so a daemon restart inside the (≤60s) window forgets it.
|
|
35
|
+
*/
|
|
36
|
+
/** Announced by a daemon that honours `attestedApproval` on daemon:tool:request. */
|
|
37
|
+
export const DAEMON_TOOL_ATTESTED_APPROVAL_CAPABILITY = 'daemon_tool_attested_approval_v1';
|
|
38
|
+
/**
|
|
39
|
+
* Announced by a daemon that honours `toolPosture: 'read_only'` on
|
|
40
|
+
* daemon:tool:request — the daemon-tool-path expression of the wire `plan`
|
|
41
|
+
* session mode: reads run, every mutating category is refused with a named
|
|
42
|
+
* reason. Carries INTENT like `browserPolicy: 'read_only'` — the daemon owns
|
|
43
|
+
* what read-only means, so the server cannot widen the fence by shipping a
|
|
44
|
+
* wider value.
|
|
45
|
+
*/
|
|
46
|
+
export const DAEMON_TOOL_READ_ONLY_CAPABILITY = 'daemon_tool_read_only_v1';
|
|
47
|
+
export const DAEMON_TOOL_CATEGORIES = [
|
|
48
|
+
'read',
|
|
49
|
+
'write',
|
|
50
|
+
'bash',
|
|
51
|
+
'browser',
|
|
52
|
+
'claude_session',
|
|
53
|
+
'native_ui',
|
|
54
|
+
];
|
|
55
|
+
/** Map a tool name to its category. Unknown tools take the restrictive answer. */
|
|
56
|
+
export function categoryForDaemonTool(toolName) {
|
|
57
|
+
switch (String(toolName || '').toLowerCase()) {
|
|
58
|
+
case 'read':
|
|
59
|
+
case 'glob':
|
|
60
|
+
case 'grep':
|
|
61
|
+
case 'filetransferread':
|
|
62
|
+
return 'read';
|
|
63
|
+
case 'write':
|
|
64
|
+
case 'edit':
|
|
65
|
+
case 'multiedit':
|
|
66
|
+
case 'apply_patch':
|
|
67
|
+
case 'filetransferwrite':
|
|
68
|
+
return 'write';
|
|
69
|
+
// Reading or stopping a background command is part of running it.
|
|
70
|
+
case 'bash':
|
|
71
|
+
case 'taskoutput':
|
|
72
|
+
case 'taskstop':
|
|
73
|
+
return 'bash';
|
|
74
|
+
case 'browser':
|
|
75
|
+
return 'browser';
|
|
76
|
+
case 'native':
|
|
77
|
+
return 'native_ui';
|
|
78
|
+
case 'claude_session':
|
|
79
|
+
case 'claude-session':
|
|
80
|
+
return 'claude_session';
|
|
81
|
+
default:
|
|
82
|
+
return 'write';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Attested approval
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* How long an attestation stays valid. Short: it answers ONE person, about
|
|
90
|
+
* THIS call, moments ago — it is not a standing grant. The server asks and
|
|
91
|
+
* dispatches back-to-back, so the window only has to outlive publish latency.
|
|
92
|
+
*/
|
|
93
|
+
export const ATTESTED_TOOL_APPROVAL_TTL_MS = 60 * 1000;
|
|
94
|
+
/**
|
|
95
|
+
* Deterministic serialization for the input digest: objects sort keys
|
|
96
|
+
* recursively, arrays keep order, non-JSON values normalize the way
|
|
97
|
+
* `JSON.stringify` would emit them (undefined/functions in objects drop, in
|
|
98
|
+
* arrays become null). Both runtimes must digest the SAME bytes — the server
|
|
99
|
+
* mints and the daemon verifies — so this lives here, once.
|
|
100
|
+
*/
|
|
101
|
+
function canonicalJson(value) {
|
|
102
|
+
if (value === null || typeof value !== 'object') {
|
|
103
|
+
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
|
|
104
|
+
return 'null';
|
|
105
|
+
}
|
|
106
|
+
return JSON.stringify(value);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
|
|
110
|
+
}
|
|
111
|
+
const record = value;
|
|
112
|
+
const keys = Object.keys(record).filter((key) => record[key] !== undefined &&
|
|
113
|
+
typeof record[key] !== 'function' &&
|
|
114
|
+
typeof record[key] !== 'symbol');
|
|
115
|
+
keys.sort();
|
|
116
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
117
|
+
}
|
|
118
|
+
/** The digest both sides compute over `toolInput`. */
|
|
119
|
+
export function canonicalToolInputDigest(toolInput) {
|
|
120
|
+
return createHash('sha256')
|
|
121
|
+
.update(canonicalJson(toolInput ?? {}))
|
|
122
|
+
.digest('hex');
|
|
123
|
+
}
|
|
124
|
+
/** Server side: mint the attestation after a person approved THIS call. */
|
|
125
|
+
export function mintAttestedToolApproval(args) {
|
|
126
|
+
const now = args.now ?? Date.now();
|
|
127
|
+
return {
|
|
128
|
+
v: 1,
|
|
129
|
+
toolUseId: args.toolUseId,
|
|
130
|
+
toolName: args.toolName,
|
|
131
|
+
inputSha256: canonicalToolInputDigest(args.toolInput),
|
|
132
|
+
decidedBy: 'user',
|
|
133
|
+
issuedAt: now,
|
|
134
|
+
expiresAt: now + (args.ttlMs ?? ATTESTED_TOOL_APPROVAL_TTL_MS),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Clock skew tolerated on `issuedAt`: a server clock slightly ahead of the
|
|
139
|
+
* daemon's must not refuse a fresh approval.
|
|
140
|
+
*/
|
|
141
|
+
export const ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS = 30 * 1000;
|
|
142
|
+
/**
|
|
143
|
+
* Daemon side: verify an attestation against the call it claims to approve.
|
|
144
|
+
* Untrusted wire input — every field is checked, and any mismatch is a plain
|
|
145
|
+
* "no approval", never a partial credit. `reason` is for daemon logs; the
|
|
146
|
+
* refusal text stays generic so a probing caller learns nothing new.
|
|
147
|
+
*/
|
|
148
|
+
export function verifyAttestedToolApproval(approval, call) {
|
|
149
|
+
if (approval === undefined || approval === null)
|
|
150
|
+
return { ok: false, reason: 'absent' };
|
|
151
|
+
if (typeof approval !== 'object' || Array.isArray(approval)) {
|
|
152
|
+
return { ok: false, reason: 'malformed' };
|
|
153
|
+
}
|
|
154
|
+
const a = approval;
|
|
155
|
+
if (a.v !== 1 ||
|
|
156
|
+
typeof a.toolUseId !== 'string' ||
|
|
157
|
+
typeof a.toolName !== 'string' ||
|
|
158
|
+
typeof a.inputSha256 !== 'string' ||
|
|
159
|
+
a.decidedBy !== 'user' ||
|
|
160
|
+
typeof a.issuedAt !== 'number' ||
|
|
161
|
+
typeof a.expiresAt !== 'number') {
|
|
162
|
+
return { ok: false, reason: 'malformed' };
|
|
163
|
+
}
|
|
164
|
+
const now = call.now ?? Date.now();
|
|
165
|
+
if (a.expiresAt <= now || a.issuedAt > now + ATTESTED_TOOL_APPROVAL_CLOCK_SKEW_MS) {
|
|
166
|
+
return { ok: false, reason: 'expired' };
|
|
167
|
+
}
|
|
168
|
+
// The TTL is the VERIFIER's bound, not the issuer's claim (OSK-12108): an
|
|
169
|
+
// attestation whose own window is longer than one approval may stay valid
|
|
170
|
+
// is refused whatever `expiresAt` says, so "answers one person about this
|
|
171
|
+
// call, moments ago" holds even against an issuer that set a year.
|
|
172
|
+
const window = a.expiresAt - a.issuedAt;
|
|
173
|
+
if (!(window > 0) || window > ATTESTED_TOOL_APPROVAL_TTL_MS) {
|
|
174
|
+
return { ok: false, reason: 'window' };
|
|
175
|
+
}
|
|
176
|
+
if (a.toolUseId !== call.toolUseId ||
|
|
177
|
+
a.toolName.toLowerCase() !== String(call.toolName).toLowerCase() ||
|
|
178
|
+
a.inputSha256 !== canonicalToolInputDigest(call.toolInput)) {
|
|
179
|
+
return { ok: false, reason: 'binding' };
|
|
180
|
+
}
|
|
181
|
+
return { ok: true };
|
|
182
|
+
}
|
|
@@ -585,11 +585,49 @@ export async function enrollWithServer(opts) {
|
|
|
585
585
|
// Enrollment is the CLI's (JWT) job; the daemon holding an opaque
|
|
586
586
|
// token should not enroll. Guarded at the daemon call site, this is
|
|
587
587
|
// defense-in-depth for any other opaque caller.
|
|
588
|
-
// • 409
|
|
589
|
-
// for
|
|
588
|
+
// • 409 ALREADY_ENROLLED with OUR thumbprint — a concurrent binary won
|
|
589
|
+
// the enrollment race for this keypair; idempotent, nothing to do.
|
|
590
|
+
//
|
|
591
|
+
// A 409 is NOT benign when the row is pinned to a DIFFERENT key
|
|
592
|
+
// (OSK-12063). The server keeps one pin per (user, machine) row while
|
|
593
|
+
// every local profile has its own key, so a 409 can mean "every refresh
|
|
594
|
+
// this key signs will be refused with DEVICE_PROOF_THUMBPRINT_MISMATCH".
|
|
595
|
+
// The server names the pinned thumbprint (`enrolledThumbprint`) so the
|
|
596
|
+
// two cases can be told apart. An older server that omits it keeps the
|
|
597
|
+
// old reading, because there is nothing to compare. A 409
|
|
598
|
+
// ENROLLMENT_DUPLICATE_KEY — this key is pinned on another active row —
|
|
599
|
+
// is never benign either.
|
|
600
|
+
let conflictCode = null;
|
|
601
|
+
let enrolledThumbprint = null;
|
|
602
|
+
if (response.status === 409) {
|
|
603
|
+
try {
|
|
604
|
+
const parsed = JSON.parse(responseText);
|
|
605
|
+
conflictCode = typeof parsed.code === 'string' ? parsed.code : null;
|
|
606
|
+
enrolledThumbprint =
|
|
607
|
+
typeof parsed.enrolledThumbprint === 'string' ? parsed.enrolledThumbprint : null;
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
/* non-JSON 409 — treat as the legacy benign race */
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const pinnedToOtherKey = response.status === 409 &&
|
|
614
|
+
enrolledThumbprint !== null &&
|
|
615
|
+
enrolledThumbprint !== keypair.thumbprint;
|
|
616
|
+
const keyPinnedElsewhere = response.status === 409 && conflictCode === 'ENROLLMENT_DUPLICATE_KEY';
|
|
590
617
|
const isBenignRejection = (response.status === 404 && responseText.includes('DAEMON_NOT_FOUND')) ||
|
|
591
618
|
(response.status === 403 && responseText.includes('OPAQUE_AUTH_REFUSED')) ||
|
|
592
|
-
response.status === 409;
|
|
619
|
+
(response.status === 409 && !pinnedToOtherKey && !keyPinnedElsewhere);
|
|
620
|
+
if (pinnedToOtherKey) {
|
|
621
|
+
emit('enrollment_key_mismatch', false, {
|
|
622
|
+
profile,
|
|
623
|
+
thumbprint: keypair.thumbprint,
|
|
624
|
+
enrolledThumbprint,
|
|
625
|
+
serverUrl,
|
|
626
|
+
message: 'this machine is registered with a different device key than this profile signs ' +
|
|
627
|
+
'with; its refreshes will be refused. An interactive daemon login (skrrd login) ' +
|
|
628
|
+
're-registers this key.',
|
|
629
|
+
}, { logLevel: 'warn' });
|
|
630
|
+
}
|
|
593
631
|
if (response.status === 404 && responseText.includes('DAEMON_NOT_FOUND')) {
|
|
594
632
|
try {
|
|
595
633
|
atomicWrite0600(backoffPath, JSON.stringify({
|
|
@@ -29,11 +29,13 @@ export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, K
|
|
|
29
29
|
export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyPair, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
|
|
30
30
|
export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
|
|
31
31
|
export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
|
|
32
|
+
export { getMachineUuid, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
32
33
|
export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
|
|
33
34
|
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';
|
|
34
35
|
export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
|
|
35
36
|
export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, harnessLoadsClaudeSkillsLayout, } from './harnessSkillLayout.js';
|
|
36
|
-
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, } from './spawnEnv.js';
|
|
37
|
+
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 { loopbackFetch, assertLoopbackHttpUrl, isLoopbackHostname, isLoopbackAddress, LoopbackTargetRefusedError, LOOPBACK_DEFAULT_MAX_BODY_BYTES, type LoopbackFetch, type LoopbackFetchInit, } from './loopbackHttp.js';
|
|
37
39
|
export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
|
|
38
40
|
export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
|
|
39
41
|
export * from './firstPartyHarness.js';
|
|
@@ -118,7 +118,12 @@ export {
|
|
|
118
118
|
initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState,
|
|
119
119
|
// Test seams — file path resolvers + state setters for spec files.
|
|
120
120
|
__publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, } from './deviceIdentityBridge.js';
|
|
121
|
+
// Hardware machine identity sent as `machineUuid` (IOPlatformUUID /
|
|
122
|
+
// machine-id / MachineGuid). ONE implementation for daemon and CLI —
|
|
123
|
+
// OSK-12063. Also exposed dependency-free as `@skrr-ai/auth-core/machine-uuid`.
|
|
124
|
+
export { getMachineUuid, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
|
|
121
125
|
// Stable per-machine UUIDv7 identity persisted at <configDir>/daemon.id.
|
|
126
|
+
// NOT a `machineUuid` — see machineUuid.ts.
|
|
122
127
|
export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
|
|
123
128
|
// Tier 1 #2 — daemon QR pairing primitives. Pure crypto + AAD framing,
|
|
124
129
|
// shared between the daemon CLI orchestrator (`daemon/src/pair.ts`) and
|
|
@@ -140,7 +145,11 @@ export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, har
|
|
|
140
145
|
// spawns the SAME engine and did not). `daemon/src/env-sanitize.ts` re-exports
|
|
141
146
|
// these so its own importers are unchanged, and keeps `filterCustomEnv` — that
|
|
142
147
|
// governs SERVER-PROVIDED env, which no other process is handed.
|
|
143
|
-
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, } from './spawnEnv.js';
|
|
148
|
+
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';
|
|
149
|
+
// OSK-12134 — the one proxy-free client for loopback HTTP. Bun's fetch (and
|
|
150
|
+
// Node's under NODE_USE_ENV_PROXY) sends 127.0.0.1 requests to HTTP_PROXY,
|
|
151
|
+
// credentials and all; every loopback caller goes through this instead.
|
|
152
|
+
export { loopbackFetch, assertLoopbackHttpUrl, isLoopbackHostname, isLoopbackAddress, LoopbackTargetRefusedError, LOOPBACK_DEFAULT_MAX_BODY_BYTES, } from './loopbackHttp.js';
|
|
144
153
|
/* Local-machine identity — the names this product leaves on a disk. Exported so
|
|
145
154
|
the CLI, daemon and Desktop share one definition instead of 47 literals. */
|
|
146
155
|
export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Upper bound on a buffered response body. Loopback control-plane bodies are small. */
|
|
2
|
+
export declare const LOOPBACK_DEFAULT_MAX_BODY_BYTES: number;
|
|
3
|
+
/** Thrown (synchronously surfaced as a rejection) for a URL that is not loopback HTTP. */
|
|
4
|
+
export declare class LoopbackTargetRefusedError extends Error {
|
|
5
|
+
readonly code = "LOOPBACK_TARGET_REFUSED";
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/** True for an IP literal in 127.0.0.0/8, `::1`, or an IPv4-mapped 127/8 address. */
|
|
9
|
+
export declare function isLoopbackAddress(address: string): boolean;
|
|
10
|
+
/** True for a host name or address this client accepts as "this machine". */
|
|
11
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Parse and validate a loopback HTTP URL. Throws {@link LoopbackTargetRefusedError}
|
|
14
|
+
* for anything that is not `http:` to a loopback host.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertLoopbackHttpUrl(input: string | URL): URL;
|
|
17
|
+
/**
|
|
18
|
+
* `RequestInit`, so a `fetch` call site can switch to {@link loopbackFetch}
|
|
19
|
+
* unchanged. Only `method`, `headers`, `signal` and a string/bytes `body` are
|
|
20
|
+
* honoured; a stream, Blob or FormData body is rejected rather than guessed at.
|
|
21
|
+
*/
|
|
22
|
+
export interface LoopbackFetchInit extends RequestInit {
|
|
23
|
+
/** Cap on the buffered response body; exceeding it rejects. */
|
|
24
|
+
maxBodyBytes?: number;
|
|
25
|
+
}
|
|
26
|
+
/** A `fetch`-shaped signature, for callers that keep a `fetchImpl` seam. */
|
|
27
|
+
export type LoopbackFetch = (input: string | URL, init?: LoopbackFetchInit) => Promise<Response>;
|
|
28
|
+
/**
|
|
29
|
+
* `fetch` for a loopback URL, over `node:http`, never through a proxy.
|
|
30
|
+
* Refuses (rejects with {@link LoopbackTargetRefusedError}) any other target.
|
|
31
|
+
*/
|
|
32
|
+
export declare const loopbackFetch: LoopbackFetch;
|