@indigoai-us/hq-cli 5.345.170 → 5.345.171
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/CHANGELOG.md +55 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +132 -5
- package/dist/command-catalog.generated.js +164 -6
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +112 -3
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +137 -14
- package/dist/commands/cloud.d.ts +58 -1
- package/dist/commands/cloud.js +219 -5
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/fleet-local.d.ts +127 -0
- package/dist/lib/bot/fleet-local.js +638 -0
- package/dist/lib/bot/index.d.ts +1 -0
- package/dist/lib/bot/index.js +1 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.js +4 -1
- package/dist/lib/daemon/sync-core-canary.d.ts +3 -0
- package/dist/lib/daemon/sync-core-canary.js +8 -3
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/fix/apply.js +54 -0
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
* HQ_VAULT_API_URL — vault-service API Gateway URL
|
|
20
20
|
*/
|
|
21
21
|
import { isExpiring, isMachineIdentity, type CognitoAuthConfig, type CognitoTokens, type ClientInfo, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
|
|
22
|
+
import { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens, type CognitoTokenSource } from "./machine-principal-state.js";
|
|
22
23
|
import { loadCachedTokens, personTokenCacheFile, readPersonTokenFile } from "./person-token-cache.js";
|
|
23
24
|
export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
24
25
|
export { isExpiring, isMachineIdentity };
|
|
25
|
-
|
|
26
|
-
export type CognitoTokenSource
|
|
26
|
+
export { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens };
|
|
27
|
+
export type { CognitoTokenSource };
|
|
27
28
|
/** Actor kind derived from the active token / machine-creds signal. */
|
|
28
29
|
export type CognitoActorKind = "human" | "agent" | "unknown";
|
|
29
30
|
/**
|
|
@@ -97,13 +98,6 @@ export declare const DEFAULT_HQ_ROOT: string;
|
|
|
97
98
|
export declare function machineTokenStateDir(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
98
99
|
/** Absolute path of the machine-token cache file (daemon state dir). */
|
|
99
100
|
export declare function machineTokenCacheFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
100
|
-
/**
|
|
101
|
-
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
102
|
-
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
103
|
-
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
104
|
-
* default-file fallback when no usable person session exists.
|
|
105
|
-
*/
|
|
106
|
-
export declare function isMachineCredsFileEnvSet(env?: NodeJS.ProcessEnv): boolean;
|
|
107
101
|
/** Default machine-creds path hq-cloud also checks when the env is unset. */
|
|
108
102
|
export declare function defaultMachineCredsFile(home?: string): string;
|
|
109
103
|
export type EnsureCognitoTokenOptions = {
|
|
@@ -127,33 +121,12 @@ export type EnsureCognitoTokenOptions = {
|
|
|
127
121
|
export declare const HQ_NO_BROWSER_LOGIN_ENV = "HQ_NO_BROWSER_LOGIN";
|
|
128
122
|
/** An explicit `interactive` option wins; otherwise interactive unless the env switch is "1". */
|
|
129
123
|
export declare function resolveInteractiveLogin(options: Pick<EnsureCognitoTokenOptions, "interactive">, env?: NodeJS.ProcessEnv): boolean;
|
|
130
|
-
/**
|
|
131
|
-
* True when a usable person Cognito cache file exists under the effective
|
|
132
|
-
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
133
|
-
* person path even when the default machine-creds file is also on disk
|
|
134
|
-
* (no-flip guarantee).
|
|
135
|
-
*/
|
|
136
|
-
export declare function hasPersonCachedSession(env?: NodeJS.ProcessEnv): boolean;
|
|
137
124
|
/**
|
|
138
125
|
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
139
126
|
* cache, and the default machine-creds file is readable. Distinct from
|
|
140
127
|
* explicit `HQ_MACHINE_CREDS_FILE` / `{ tokenSource: "machine" }` opt-in.
|
|
141
128
|
*/
|
|
142
129
|
export declare function isDefaultMachineCredsFallback(options?: Pick<EnsureCognitoTokenOptions, "tokenSource">, env?: NodeJS.ProcessEnv): boolean;
|
|
143
|
-
/**
|
|
144
|
-
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
145
|
-
*
|
|
146
|
-
* Machine path when:
|
|
147
|
-
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
148
|
-
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
149
|
-
* - fresh-box fallback: env unset, no person cache, default
|
|
150
|
-
* `~/.hq-agent/machine-creds.json` readable.
|
|
151
|
-
*
|
|
152
|
-
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
153
|
-
* person cache keeps the person path even when the default creds file exists
|
|
154
|
-
* (do not flip a human's commands onto machine tokens).
|
|
155
|
-
*/
|
|
156
|
-
export declare function wantsMachineCognitoTokens(options?: Pick<EnsureCognitoTokenOptions, "tokenSource">, env?: NodeJS.ProcessEnv): boolean;
|
|
157
130
|
/**
|
|
158
131
|
* Which Cognito token source this process will use for doctor / status / whoami.
|
|
159
132
|
* Matches {@link wantsMachineCognitoTokens}: env opt-in, or default-file
|
|
@@ -26,6 +26,7 @@ import * as path from "path";
|
|
|
26
26
|
import * as yaml from "js-yaml";
|
|
27
27
|
import chalk from "chalk";
|
|
28
28
|
import { isExpiring, refreshTokens, saveCachedTokens, browserLogin, detectHqCoreVersion, isMachineIdentity, getValidMachineTokens, CognitoRefreshError, accessTokenFingerprint, invalidateCachedTokensByFingerprint, } from "@indigoai-us/hq-cloud";
|
|
29
|
+
import { hasPersonCachedSession, isMachineCredsFileEnvSet, probeMachineIdentity, wantsMachineCognitoTokens, } from "./machine-principal-state.js";
|
|
29
30
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
30
31
|
import { daemonDir } from "../lib/mesh/live/daemon/paths.js";
|
|
31
32
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
@@ -36,6 +37,7 @@ export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
|
36
37
|
// Re-export the cached-session primitives for callers that must deliberately
|
|
37
38
|
// avoid the refresh/login behavior in the higher-level helpers below.
|
|
38
39
|
export { isExpiring, isMachineIdentity };
|
|
40
|
+
export { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens };
|
|
39
41
|
/**
|
|
40
42
|
* Override for where machine-minted Cognito tokens are cached.
|
|
41
43
|
* Default: the work-mesh daemon state dir (`~/.hq/work-mesh/daemon`), so a
|
|
@@ -253,16 +255,6 @@ export function machineTokenStateDir(home = os.homedir(), env = process.env) {
|
|
|
253
255
|
export function machineTokenCacheFile(home = os.homedir(), env = process.env) {
|
|
254
256
|
return path.join(machineTokenStateDir(home, env), "cognito-tokens.json");
|
|
255
257
|
}
|
|
256
|
-
/**
|
|
257
|
-
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
258
|
-
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
259
|
-
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
260
|
-
* default-file fallback when no usable person session exists.
|
|
261
|
-
*/
|
|
262
|
-
export function isMachineCredsFileEnvSet(env = process.env) {
|
|
263
|
-
const raw = env.HQ_MACHINE_CREDS_FILE;
|
|
264
|
-
return typeof raw === "string" && raw.trim().length > 0;
|
|
265
|
-
}
|
|
266
258
|
/** Default machine-creds path hq-cloud also checks when the env is unset. */
|
|
267
259
|
export function defaultMachineCredsFile(home = os.homedir()) {
|
|
268
260
|
return path.join(home, ".hq-agent", "machine-creds.json");
|
|
@@ -279,69 +271,6 @@ export function resolveInteractiveLogin(options, env = process.env) {
|
|
|
279
271
|
return options.interactive;
|
|
280
272
|
return env[HQ_NO_BROWSER_LOGIN_ENV]?.trim() !== "1";
|
|
281
273
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Align `process.env.HQ_MACHINE_CREDS_FILE` with a (possibly synthetic) `env`
|
|
284
|
-
* for the duration of `fn`, so hq-cloud's `isMachineIdentity()` sees the same
|
|
285
|
-
* value doctor / tests intended.
|
|
286
|
-
*/
|
|
287
|
-
function withAlignedMachineCredsEnv(env, fn) {
|
|
288
|
-
const prevCreds = process.env.HQ_MACHINE_CREDS_FILE;
|
|
289
|
-
const nextCreds = env.HQ_MACHINE_CREDS_FILE;
|
|
290
|
-
if (nextCreds !== prevCreds) {
|
|
291
|
-
if (nextCreds === undefined || String(nextCreds).trim() === "") {
|
|
292
|
-
delete process.env.HQ_MACHINE_CREDS_FILE;
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
process.env.HQ_MACHINE_CREDS_FILE = nextCreds;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
return fn();
|
|
300
|
-
}
|
|
301
|
-
finally {
|
|
302
|
-
if (nextCreds !== prevCreds) {
|
|
303
|
-
if (prevCreds === undefined)
|
|
304
|
-
delete process.env.HQ_MACHINE_CREDS_FILE;
|
|
305
|
-
else
|
|
306
|
-
process.env.HQ_MACHINE_CREDS_FILE = prevCreds;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* True when a usable person Cognito cache file exists under the effective
|
|
312
|
-
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
313
|
-
* person path even when the default machine-creds file is also on disk
|
|
314
|
-
* (no-flip guarantee).
|
|
315
|
-
*/
|
|
316
|
-
export function hasPersonCachedSession(env = process.env) {
|
|
317
|
-
try {
|
|
318
|
-
const file = personTokenCacheFile(os.homedir(), env);
|
|
319
|
-
const raw = readPersonTokenFile(os.homedir(), env);
|
|
320
|
-
if (!raw ||
|
|
321
|
-
typeof raw.accessToken !== "string" ||
|
|
322
|
-
raw.accessToken.length === 0 ||
|
|
323
|
-
typeof raw.idToken !== "string" ||
|
|
324
|
-
raw.idToken.length === 0) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
// Definitive refresh rejection writes `${file}.invalid.<fingerprint>`;
|
|
328
|
-
// treat that as no usable person session so status/whoami report machine.
|
|
329
|
-
const marker = `${file}.invalid.${accessTokenFingerprint(raw.accessToken)}`;
|
|
330
|
-
if (fs.existsSync(marker))
|
|
331
|
-
return false;
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Whether hq-cloud currently sees readable machine creds (env override or
|
|
340
|
-
* default `~/.hq-agent/machine-creds.json`), honoring a synthetic `env`.
|
|
341
|
-
*/
|
|
342
|
-
function probeMachineIdentity(env) {
|
|
343
|
-
return withAlignedMachineCredsEnv(env, () => isMachineIdentity());
|
|
344
|
-
}
|
|
345
274
|
/**
|
|
346
275
|
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
347
276
|
* cache, and the default machine-creds file is readable. Distinct from
|
|
@@ -361,33 +290,6 @@ export function isDefaultMachineCredsFallback(options = {}, env = process.env) {
|
|
|
361
290
|
function logDefaultMachineCredsFallback() {
|
|
362
291
|
console.error(chalk.dim("machine identity from default creds file"));
|
|
363
292
|
}
|
|
364
|
-
/**
|
|
365
|
-
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
366
|
-
*
|
|
367
|
-
* Machine path when:
|
|
368
|
-
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
369
|
-
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
370
|
-
* - fresh-box fallback: env unset, no person cache, default
|
|
371
|
-
* `~/.hq-agent/machine-creds.json` readable.
|
|
372
|
-
*
|
|
373
|
-
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
374
|
-
* person cache keeps the person path even when the default creds file exists
|
|
375
|
-
* (do not flip a human's commands onto machine tokens).
|
|
376
|
-
*/
|
|
377
|
-
export function wantsMachineCognitoTokens(options = {}, env = process.env) {
|
|
378
|
-
if (options.tokenSource === "person")
|
|
379
|
-
return false;
|
|
380
|
-
if (options.tokenSource === "machine") {
|
|
381
|
-
return probeMachineIdentity(env);
|
|
382
|
-
}
|
|
383
|
-
if (isMachineCredsFileEnvSet(env)) {
|
|
384
|
-
return probeMachineIdentity(env);
|
|
385
|
-
}
|
|
386
|
-
// Fresh-box fallback — default creds file only when no person session.
|
|
387
|
-
if (hasPersonCachedSession(env))
|
|
388
|
-
return false;
|
|
389
|
-
return probeMachineIdentity(env);
|
|
390
|
-
}
|
|
391
293
|
/**
|
|
392
294
|
* Which Cognito token source this process will use for doctor / status / whoami.
|
|
393
295
|
* Matches {@link wantsMachineCognitoTokens}: env opt-in, or default-file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Person browser/login cache vs machine-creds USER_PASSWORD_AUTH mint. */
|
|
2
|
+
export type CognitoTokenSource = "person" | "machine";
|
|
3
|
+
/**
|
|
4
|
+
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
5
|
+
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
6
|
+
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
7
|
+
* default-file fallback when no usable person session exists.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isMachineCredsFileEnvSet(env?: NodeJS.ProcessEnv): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* True when a usable person Cognito cache file exists under the effective
|
|
12
|
+
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
13
|
+
* person path even when the default machine-creds file is also on disk
|
|
14
|
+
* (no-flip guarantee).
|
|
15
|
+
*/
|
|
16
|
+
export declare function hasPersonCachedSession(env?: NodeJS.ProcessEnv): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether hq-cloud currently sees readable machine creds (env override or
|
|
19
|
+
* default `~/.hq-agent/machine-creds.json`), honoring a synthetic `env`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function probeMachineIdentity(env: NodeJS.ProcessEnv): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
24
|
+
*
|
|
25
|
+
* Machine path when:
|
|
26
|
+
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
27
|
+
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
28
|
+
* - fresh-box fallback: env unset, no person cache, default
|
|
29
|
+
* `~/.hq-agent/machine-creds.json` readable.
|
|
30
|
+
*
|
|
31
|
+
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
32
|
+
* person cache keeps the person path even when the default creds file exists
|
|
33
|
+
* (do not flip a human's commands onto machine tokens).
|
|
34
|
+
*/
|
|
35
|
+
export declare function wantsMachineCognitoTokens(options?: {
|
|
36
|
+
tokenSource?: CognitoTokenSource;
|
|
37
|
+
}, env?: NodeJS.ProcessEnv): boolean;
|
|
38
|
+
//# sourceMappingURL=machine-principal-state.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-state checks that decide machine vs person Cognito tokens. Reads env
|
|
3
|
+
* and files only; no network code, so fast native entries can import it
|
|
4
|
+
* (e.g. via src/lib/caller-principal.ts) without pulling cognito-session.ts.
|
|
5
|
+
*/
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import { accessTokenFingerprint, isMachineIdentity } from "@indigoai-us/hq-cloud";
|
|
9
|
+
import { personTokenCacheFile, readPersonTokenFile } from "./person-token-cache.js";
|
|
10
|
+
/**
|
|
11
|
+
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
12
|
+
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
13
|
+
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
14
|
+
* default-file fallback when no usable person session exists.
|
|
15
|
+
*/
|
|
16
|
+
export function isMachineCredsFileEnvSet(env = process.env) {
|
|
17
|
+
const raw = env.HQ_MACHINE_CREDS_FILE;
|
|
18
|
+
return typeof raw === "string" && raw.trim().length > 0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Align `process.env.HQ_MACHINE_CREDS_FILE` with a (possibly synthetic) `env`
|
|
22
|
+
* for the duration of `fn`, so hq-cloud's `isMachineIdentity()` sees the same
|
|
23
|
+
* value doctor / tests intended.
|
|
24
|
+
*/
|
|
25
|
+
function withAlignedMachineCredsEnv(env, fn) {
|
|
26
|
+
const prevCreds = process.env.HQ_MACHINE_CREDS_FILE;
|
|
27
|
+
const nextCreds = env.HQ_MACHINE_CREDS_FILE;
|
|
28
|
+
if (nextCreds !== prevCreds) {
|
|
29
|
+
if (nextCreds === undefined || String(nextCreds).trim() === "") {
|
|
30
|
+
delete process.env.HQ_MACHINE_CREDS_FILE;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
process.env.HQ_MACHINE_CREDS_FILE = nextCreds;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return fn();
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
if (nextCreds !== prevCreds) {
|
|
41
|
+
if (prevCreds === undefined)
|
|
42
|
+
delete process.env.HQ_MACHINE_CREDS_FILE;
|
|
43
|
+
else
|
|
44
|
+
process.env.HQ_MACHINE_CREDS_FILE = prevCreds;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* True when a usable person Cognito cache file exists under the effective
|
|
50
|
+
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
51
|
+
* person path even when the default machine-creds file is also on disk
|
|
52
|
+
* (no-flip guarantee).
|
|
53
|
+
*/
|
|
54
|
+
export function hasPersonCachedSession(env = process.env) {
|
|
55
|
+
try {
|
|
56
|
+
const file = personTokenCacheFile(os.homedir(), env);
|
|
57
|
+
const raw = readPersonTokenFile(os.homedir(), env);
|
|
58
|
+
if (!raw ||
|
|
59
|
+
typeof raw.accessToken !== "string" ||
|
|
60
|
+
raw.accessToken.length === 0 ||
|
|
61
|
+
typeof raw.idToken !== "string" ||
|
|
62
|
+
raw.idToken.length === 0) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// Definitive refresh rejection writes `${file}.invalid.<fingerprint>`;
|
|
66
|
+
// treat that as no usable person session so status/whoami report machine.
|
|
67
|
+
const marker = `${file}.invalid.${accessTokenFingerprint(raw.accessToken)}`;
|
|
68
|
+
if (fs.existsSync(marker))
|
|
69
|
+
return false;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether hq-cloud currently sees readable machine creds (env override or
|
|
78
|
+
* default `~/.hq-agent/machine-creds.json`), honoring a synthetic `env`.
|
|
79
|
+
*/
|
|
80
|
+
export function probeMachineIdentity(env) {
|
|
81
|
+
return withAlignedMachineCredsEnv(env, () => isMachineIdentity());
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
85
|
+
*
|
|
86
|
+
* Machine path when:
|
|
87
|
+
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
88
|
+
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
89
|
+
* - fresh-box fallback: env unset, no person cache, default
|
|
90
|
+
* `~/.hq-agent/machine-creds.json` readable.
|
|
91
|
+
*
|
|
92
|
+
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
93
|
+
* person cache keeps the person path even when the default creds file exists
|
|
94
|
+
* (do not flip a human's commands onto machine tokens).
|
|
95
|
+
*/
|
|
96
|
+
export function wantsMachineCognitoTokens(options = {}, env = process.env) {
|
|
97
|
+
if (options.tokenSource === "person")
|
|
98
|
+
return false;
|
|
99
|
+
if (options.tokenSource === "machine") {
|
|
100
|
+
return probeMachineIdentity(env);
|
|
101
|
+
}
|
|
102
|
+
if (isMachineCredsFileEnvSet(env)) {
|
|
103
|
+
return probeMachineIdentity(env);
|
|
104
|
+
}
|
|
105
|
+
// Fresh-box fallback — default creds file only when no person session.
|
|
106
|
+
if (hasPersonCachedSession(env))
|
|
107
|
+
return false;
|
|
108
|
+
return probeMachineIdentity(env);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=machine-principal-state.js.map
|
package/dist/utils/safe-link.js
CHANGED
|
@@ -284,6 +284,9 @@ export function removeLinkOnly(linkPath) {
|
|
|
284
284
|
if (!st.isSymbolicLink()) {
|
|
285
285
|
throw new Error(`Refusing to remove ${linkPath}: it is not a link.`);
|
|
286
286
|
}
|
|
287
|
+
// Re-check immediately before each removal call, so an entry swapped for a
|
|
288
|
+
// real file or folder after the first check is refused, not removed.
|
|
289
|
+
assertStillLink(linkPath);
|
|
287
290
|
try {
|
|
288
291
|
fs.unlinkSync(linkPath);
|
|
289
292
|
}
|
|
@@ -293,8 +296,14 @@ export function removeLinkOnly(linkPath) {
|
|
|
293
296
|
// unlink(). A non-recursive rmdir removes only the junction entry.
|
|
294
297
|
if (code !== 'EPERM' && code !== 'EISDIR')
|
|
295
298
|
throw err;
|
|
299
|
+
assertStillLink(linkPath);
|
|
296
300
|
fs.rmdirSync(linkPath);
|
|
297
301
|
}
|
|
298
302
|
return true;
|
|
299
303
|
}
|
|
304
|
+
function assertStillLink(linkPath) {
|
|
305
|
+
if (!fs.lstatSync(linkPath).isSymbolicLink()) {
|
|
306
|
+
throw new Error(`Refusing to remove ${linkPath}: it is no longer a link.`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
300
309
|
//# sourceMappingURL=safe-link.js.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bind a script-locked secret release to the process that is actually run
|
|
3
|
+
* (feedback_0e6abcb8).
|
|
4
|
+
*
|
|
5
|
+
* `--script <path>` sends the script's path and hash to the vault, which
|
|
6
|
+
* releases a script-locked secret when that pair is approved. Before this
|
|
7
|
+
* module the command after `--` was never compared with that script, so any
|
|
8
|
+
* command that named an approved script received the secret.
|
|
9
|
+
*
|
|
10
|
+
* When any requested secret is script-locked (or its lock state is unknown),
|
|
11
|
+
* the command after `--` must be one of:
|
|
12
|
+
* 1. the approved script itself: `-- ./approved.sh [args...]` (realpath
|
|
13
|
+
* match). Its `#!` line is parsed and the interpreter it names goes
|
|
14
|
+
* through the same allowlist; `#!/usr/bin/env X` resolves X against a
|
|
15
|
+
* fixed system PATH, never the caller's PATH;
|
|
16
|
+
* 2. an allowlisted interpreter followed directly by the script:
|
|
17
|
+
* `-- bash approved.sh [args...]`, also resolved against the fixed PATH.
|
|
18
|
+
* Anything else (wrappers such as env, xargs, sudo, a shell `-c` string, an
|
|
19
|
+
* interpreter option before the script) is refused and nothing is injected.
|
|
20
|
+
*
|
|
21
|
+
* Allowlisted interpreters: sh, dash, ksh, bash (run with --noprofile
|
|
22
|
+
* --norc), zsh (run with -f), python3/python (run with -I -S), node. Ruby and
|
|
23
|
+
* perl are not allowlisted. The interpreter's realpath and every parent
|
|
24
|
+
* directory must be owned by root and not group- or world-writable, so
|
|
25
|
+
* Homebrew or other user-writable installs are refused.
|
|
26
|
+
*
|
|
27
|
+
* At spawn time the script bytes are read once, hashed, compared with the hash
|
|
28
|
+
* sent to the vault, written to a fresh 0700 private directory, re-hashed, and
|
|
29
|
+
* that copy is passed to the interpreter. The original path is exposed to the
|
|
30
|
+
* script as HQ_APPROVED_SCRIPT and its directory as HQ_APPROVED_SCRIPT_DIR.
|
|
31
|
+
* `$0` / `__file__` / `__dirname` name the copy, so a script that loads
|
|
32
|
+
* neighbour files relative to itself must use HQ_APPROVED_SCRIPT_DIR. The
|
|
33
|
+
* working directory is left as the caller's.
|
|
34
|
+
*
|
|
35
|
+
* The child environment gets a fixed system PATH and loses the variables that
|
|
36
|
+
* make an interpreter, the loader or the macOS xcrun shims run other code.
|
|
37
|
+
* The same names are refused when they arrive in the injected secrets or in
|
|
38
|
+
* an env-schema / env-local value, because those are merged last.
|
|
39
|
+
*
|
|
40
|
+
* The child environment is built from an allowlist (see scriptBoundChildEnv),
|
|
41
|
+
* not inherited from the caller.
|
|
42
|
+
*
|
|
43
|
+
* What the binding does and does not protect: it controls which entrypoint
|
|
44
|
+
* receives the secret. It does not isolate what that entrypoint runs. A
|
|
45
|
+
* same-user caller can still influence tools the script calls through files
|
|
46
|
+
* in HOME (for example ~/.gitconfig and fsmonitor hooks, ~/.curlrc,
|
|
47
|
+
* ~/.ssh/config, ~/.netrc) or through anything else the user can write. Real
|
|
48
|
+
* isolation needs the vault-side sandbox release (`hq secrets sandbox`).
|
|
49
|
+
*
|
|
50
|
+
* Remaining limits, documented on purpose:
|
|
51
|
+
* - Only the script file is hashed. Files it sources, requires or imports
|
|
52
|
+
* (neighbour files, modules) are not covered by the approval.
|
|
53
|
+
* - A process running as the same user can still race the private copy
|
|
54
|
+
* between its re-hash and the interpreter opening it. The window is short
|
|
55
|
+
* and the path is random, but same-user code is not a boundary this
|
|
56
|
+
* client-side check can enforce.
|
|
57
|
+
* - The check runs in the CLI. A modified CLI, or a direct call to the vault
|
|
58
|
+
* API with a self-asserted hash, is outside what it can stop.
|
|
59
|
+
*/
|
|
60
|
+
export declare class ScriptBindingError extends Error {
|
|
61
|
+
constructor(message: string);
|
|
62
|
+
}
|
|
63
|
+
/** Root-owned search path used for interpreters and for the child. */
|
|
64
|
+
export declare const FIXED_SYSTEM_PATH = "/usr/bin:/bin:/usr/sbin:/sbin";
|
|
65
|
+
export interface ScriptBoundCommand {
|
|
66
|
+
scriptRealPath: string;
|
|
67
|
+
interpreterPath: string;
|
|
68
|
+
interpreterArgs: string[];
|
|
69
|
+
scriptArgs: string[];
|
|
70
|
+
/** sha256 of the script when the command was resolved. */
|
|
71
|
+
sha256: string;
|
|
72
|
+
}
|
|
73
|
+
export interface PreparedBoundSpawn {
|
|
74
|
+
file: string;
|
|
75
|
+
args: string[];
|
|
76
|
+
copyPath: string;
|
|
77
|
+
cleanup: () => void;
|
|
78
|
+
}
|
|
79
|
+
/** True for a variable a bound script's environment must not take from the caller. */
|
|
80
|
+
export declare function isDeniedEnvName(name: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Schema / local env literal names a bound `hq run --script` launch accepts
|
|
83
|
+
* (fifth review). Literals are caller-written, so instead of denying
|
|
84
|
+
* tool-config names one by one, only these app-level switches are allowed.
|
|
85
|
+
* Everything else must come from the vault through hq().
|
|
86
|
+
*/
|
|
87
|
+
export declare const SAFE_LITERAL_NAMES: Set<string>;
|
|
88
|
+
/** Refuse schema literals that are not on SAFE_LITERAL_NAMES (or are denied). */
|
|
89
|
+
export declare function assertLiteralNamesAllowed(names: Iterable<string>): void;
|
|
90
|
+
/**
|
|
91
|
+
* Design (feedback_0e6abcb8, fourth review): an injected name is accepted in a
|
|
92
|
+
* bound launch only when it is not on the loader/interpreter denylist, not
|
|
93
|
+
* one of the inherited allowlist names (HOME, USER, LANG, LC_*, TZ, TMPDIR,
|
|
94
|
+
* ...), and not on the explicit dangerous list above. Ordinary application
|
|
95
|
+
* names (FAKE_API_KEY, DATABASE_URL, NODE_ENV) pass.
|
|
96
|
+
*/
|
|
97
|
+
export declare function isDeniedInjectedName(name: string): boolean;
|
|
98
|
+
/** Refuse a bound launch whose injected values use a denied name. */
|
|
99
|
+
export declare function assertInjectedNamesAllowed(names: Iterable<string>): void;
|
|
100
|
+
export declare function privateCopyRoot(): string;
|
|
101
|
+
/**
|
|
102
|
+
* True when realPath and every parent directory are owned by root and not
|
|
103
|
+
* group- or world-writable.
|
|
104
|
+
*/
|
|
105
|
+
export declare function isRootOwnedChain(realPath: string): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the command after `--` against the approved script. Throws
|
|
108
|
+
* ScriptBindingError when the command is not exactly the script invocation.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveScriptBoundCommand(scriptPath: string, childArgs: string[]): Promise<ScriptBoundCommand>;
|
|
111
|
+
/** Compare the script on disk with the hash sent to the vault. */
|
|
112
|
+
export declare function assertScriptUnchanged(bound: ScriptBoundCommand, attestedSha256: string | undefined): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Read the script once, check its hash, copy those bytes into a fresh 0700
|
|
115
|
+
* directory, re-hash the copy, and return the spawn target that runs the copy.
|
|
116
|
+
*/
|
|
117
|
+
export declare function prepareBoundSpawn(bound: ScriptBoundCommand, attestedSha256: string | undefined): PreparedBoundSpawn;
|
|
118
|
+
/**
|
|
119
|
+
* Remove the private copy when the parent gets SIGINT, SIGTERM or SIGHUP, or
|
|
120
|
+
* exits, then re-raise the signal so the default termination still happens.
|
|
121
|
+
* SIGKILL cannot be handled; a copy left by it stays in the 0700 temp
|
|
122
|
+
* directory until the OS cleans the temp dir.
|
|
123
|
+
*/
|
|
124
|
+
export declare function cleanupOnParentSignals(cleanup: () => void, proc?: Pick<NodeJS.Process, "once" | "removeListener" | "kill" | "pid">): () => void;
|
|
125
|
+
/**
|
|
126
|
+
* Child environment for a bound script, built from an allowlist: HOME, USER,
|
|
127
|
+
* LOGNAME, LANG, LC_*, TERM, TZ and TMPDIR from the caller, a fixed PATH, and
|
|
128
|
+
* the HQ_APPROVED_* variables. Injected secrets and schema values are added by
|
|
129
|
+
* the caller after a denylist check.
|
|
130
|
+
*/
|
|
131
|
+
export declare function scriptBoundChildEnv(env: NodeJS.ProcessEnv, scriptRealPath?: string): NodeJS.ProcessEnv;
|
|
132
|
+
/**
|
|
133
|
+
* Whether the release must be bound to the script. Only rows the vault marked
|
|
134
|
+
* `scriptLock.mode: "off"` are exempt; enforced or unknown fails closed.
|
|
135
|
+
*/
|
|
136
|
+
export declare function requiresScriptBinding(keys: string[], lockModes: Map<string, string | undefined>): boolean;
|
|
137
|
+
export interface ScriptBindingPlan {
|
|
138
|
+
bound?: ScriptBoundCommand;
|
|
139
|
+
error?: ScriptBindingError;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the binding before any fetch. A refusal is held, not thrown, so a
|
|
143
|
+
* caller whose secrets turn out not to be script-locked keeps working.
|
|
144
|
+
*/
|
|
145
|
+
export declare function planScriptBinding(scriptPath: string | undefined, childArgs: string[]): Promise<ScriptBindingPlan | undefined>;
|
|
146
|
+
export interface ChildLaunch {
|
|
147
|
+
file: string;
|
|
148
|
+
args: string[];
|
|
149
|
+
env: NodeJS.ProcessEnv;
|
|
150
|
+
cleanup: () => void;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Pick what to spawn once the vault has answered. Unlocked secrets keep the
|
|
154
|
+
* caller's command unchanged; enforced or unknown lock state requires the
|
|
155
|
+
* bound script and throws ScriptBindingError otherwise.
|
|
156
|
+
*/
|
|
157
|
+
export declare function selectChildLaunch(plan: ScriptBindingPlan | undefined, keys: string[], lockModes: Map<string, string | undefined>, attestedSha256: string | undefined, childArgs: string[], env: NodeJS.ProcessEnv, injected: Record<string, string>): ChildLaunch;
|
|
158
|
+
//# sourceMappingURL=script-bound-exec.d.ts.map
|