@indigoai-us/hq-cli 5.47.10 → 5.47.12

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.
@@ -1,10 +1,12 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bdef03fd-2f30-5034-8f32-23a5eb77cc3d")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d01cb1a1-b851-5ffe-9608-e505e55dcdc8")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import * as readline from "node:readline";
5
5
  import { spawn } from "node:child_process";
6
+ import * as nodePath from "node:path";
6
7
  import { ensureCognitoToken } from "../utils/cognito-session.js";
7
- import { readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
8
+ import { DEFAULT_SECRETS_CACHE_TTL_MS, readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
9
+ import { computeSha256 } from "../utils/integrity.js";
8
10
  import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN } from "./_patterns.js";
9
11
  import { vaultApiFetch, getCompanyUid, getEntityUid, } from "../utils/vault-api.js";
10
12
  export { vaultApiFetch, getCompanyUid, getEntityUid };
@@ -128,6 +130,86 @@ function promptSecretInteractively() {
128
130
  // large --only list is chunked client-side rather than 400'd whole by the
129
131
  // server (the legacy per-key GET path had no such cap).
130
132
  const MAX_BATCH_NAMES = 100;
133
+ function normalizeSecretTier(tier) {
134
+ return tier === "sensitive" || tier === "nuclear" ? tier : "standard";
135
+ }
136
+ function normalizeScriptLockMode(mode) {
137
+ return mode === "enforced" ? "enforced" : "off";
138
+ }
139
+ function normalizeCacheTtlMs(cacheTtlMs) {
140
+ return typeof cacheTtlMs === "number"
141
+ ? cacheTtlMs
142
+ : DEFAULT_SECRETS_CACHE_TTL_MS;
143
+ }
144
+ function extractApiMessage(body, fallback) {
145
+ const message = typeof body.message === "string" ? body.message : undefined;
146
+ const error = typeof body.error === "string" ? body.error : undefined;
147
+ return message ?? error ?? fallback;
148
+ }
149
+ async function buildSecretUsage(channel, scriptPath, scriptId, attestationLevel = "self-asserted-hash") {
150
+ if (!scriptPath) {
151
+ return { channel };
152
+ }
153
+ const resolvedPath = nodePath.resolve(scriptPath);
154
+ return {
155
+ channel,
156
+ script: {
157
+ scriptId: scriptId ?? resolvedPath,
158
+ path: resolvedPath,
159
+ sha256: await computeSha256(resolvedPath),
160
+ attestationLevel,
161
+ },
162
+ };
163
+ }
164
+ function normalizePolicyRecord(secretPath, data) {
165
+ const policy = data.policy ?? { path: secretPath };
166
+ const scripts = Array.isArray(policy.scripts)
167
+ ? policy.scripts
168
+ : Array.isArray(data.scripts)
169
+ ? data.scripts
170
+ : [];
171
+ return {
172
+ path: policy.path ?? secretPath,
173
+ tier: normalizeSecretTier(policy.tier),
174
+ scriptLock: {
175
+ mode: normalizeScriptLockMode(policy.scriptLock?.mode),
176
+ requiredAttestation: policy.scriptLock?.requiredAttestation,
177
+ },
178
+ scripts,
179
+ };
180
+ }
181
+ function renderPolicySummary(policy) {
182
+ console.log(chalk.bold(`Policy: ${policy.path}`));
183
+ console.log(` Tier: ${normalizeSecretTier(policy.tier)}`);
184
+ console.log(` Script Lock: ${normalizeScriptLockMode(policy.scriptLock?.mode)}`);
185
+ if (policy.scriptLock?.requiredAttestation) {
186
+ console.log(` Attestation: ${policy.scriptLock.requiredAttestation}`);
187
+ }
188
+ }
189
+ function renderPolicyScripts(scripts) {
190
+ if (scripts.length === 0) {
191
+ console.log(chalk.dim("No approved scripts."));
192
+ return;
193
+ }
194
+ const idWidth = Math.max(2, ...scripts.map((script) => script.scriptId.length));
195
+ const pathWidth = Math.max(6, ...scripts.map((script) => script.scriptPath.length));
196
+ const attestationWidth = Math.max(11, ...scripts.map((script) => script.attestationLevel.length));
197
+ const header = [
198
+ "ID".padEnd(idWidth),
199
+ "SCRIPT".padEnd(pathWidth),
200
+ "ATTESTATION".padEnd(attestationWidth),
201
+ "SHA256",
202
+ ].join(" ");
203
+ console.log(chalk.bold(header));
204
+ for (const script of scripts) {
205
+ console.log([
206
+ script.scriptId.padEnd(idWidth),
207
+ script.scriptPath.padEnd(pathWidth),
208
+ script.attestationLevel.padEnd(attestationWidth),
209
+ script.sha256,
210
+ ].join(" "));
211
+ }
212
+ }
131
213
  // HQ-4H — load + decrypt secrets through the BATCH-LOAD endpoint
132
214
  // (`POST /secrets/{companyUid}/load`) instead of one single-secret GET per key.
133
215
  //
@@ -146,7 +228,7 @@ const MAX_BATCH_NAMES = 100;
146
228
  // Cache-first (so warm keys cost no request), chunked at MAX_BATCH_NAMES, and
147
229
  // throws on the FIRST unresolved key with the same `Failed to fetch secret
148
230
  // '<k>': <reason>` shape the per-key GET path used — never swallows a failure.
149
- export async function loadRevealedSecrets(token, companyUid, keys) {
231
+ export async function loadRevealedSecrets(token, companyUid, keys, usage) {
150
232
  const resolved = new Map();
151
233
  const missing = [];
152
234
  for (const key of keys) {
@@ -164,18 +246,27 @@ export async function loadRevealedSecrets(token, companyUid, keys) {
164
246
  token,
165
247
  path: `/secrets/${encodeURIComponent(companyUid)}/load`,
166
248
  method: "POST",
167
- body: { names: chunk },
249
+ body: usage ? { names: chunk, usage } : { names: chunk },
168
250
  });
169
251
  if (!res.ok) {
170
252
  const body = (await res.json().catch(() => ({})));
171
- throw new Error(`Failed to batch-load secrets: ${body.error ?? res.statusText}`);
253
+ const message = extractApiMessage(body, res.statusText);
254
+ if (res.status >= 400 &&
255
+ res.status < 500 &&
256
+ typeof body.code === "string") {
257
+ throw new Error(message);
258
+ }
259
+ throw new Error(`Failed to batch-load secrets: ${message}`);
172
260
  }
173
261
  const data = (await res.json());
174
262
  for (const s of data.secrets ?? []) {
175
263
  if (s.value == null) {
176
264
  throw new Error(`Secret '${s.name}' has no value (reveal may not be permitted).`);
177
265
  }
178
- writeCache(companyUid, s.name, s.value);
266
+ const cacheTtlMs = normalizeCacheTtlMs(s.cacheTtlMs);
267
+ if (cacheTtlMs > 0) {
268
+ writeCache(companyUid, s.name, s.value, cacheTtlMs);
269
+ }
179
270
  resolved.set(s.name, s.value);
180
271
  }
181
272
  const errorsByName = new Map();
@@ -267,21 +358,26 @@ export function registerSecretsCommand(program) {
267
358
  try {
268
359
  const token = await ensureCognitoToken();
269
360
  const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
270
- const query = {};
271
- if (opts.reveal) {
272
- query.reveal = "true";
273
- }
274
361
  const res = await vaultApiFetch({
275
362
  token,
276
363
  path: buildSecretNamePath(companyUid, name),
277
- query: Object.keys(query).length > 0 ? query : undefined,
278
364
  });
279
365
  if (!res.ok) {
280
- const body = await res.json().catch(() => ({}));
281
- console.error(chalk.red(`Failed to get secret: ${body.error ?? res.statusText}`));
366
+ const body = (await res.json().catch(() => ({})));
367
+ console.error(chalk.red(`Failed to get secret: ${extractApiMessage(body, res.statusText)}`));
282
368
  process.exit(1);
283
369
  }
284
370
  const data = (await res.json());
371
+ let revealedValue;
372
+ let revealError = null;
373
+ if (opts.reveal) {
374
+ try {
375
+ revealedValue = (await loadRevealedSecrets(token, companyUid, [name], await buildSecretUsage("reveal"))).get(name);
376
+ }
377
+ catch (err) {
378
+ revealError = err instanceof Error ? err.message : String(err);
379
+ }
380
+ }
285
381
  const s = data.secret;
286
382
  console.log(chalk.bold(`Secret: ${s.name}`));
287
383
  if (s.lastModifiedDate) {
@@ -290,12 +386,18 @@ export function registerSecretsCommand(program) {
290
386
  if (s.version != null) {
291
387
  console.log(` Version: ${s.version}`);
292
388
  }
293
- if (opts.reveal && s.value != null) {
294
- console.log(` Value: ${s.value}`);
389
+ console.log(` Tier: ${normalizeSecretTier(s.tier)}`);
390
+ console.log(` Script Lock: ${normalizeScriptLockMode(s.scriptLock?.mode)}`);
391
+ if (opts.reveal && revealedValue != null) {
392
+ console.log(` Value: ${revealedValue}`);
295
393
  }
296
394
  else {
297
395
  console.log(` Value: ${chalk.dim("[REDACTED]")}`);
298
396
  }
397
+ if (revealError) {
398
+ console.error(chalk.red(revealError));
399
+ process.exit(1);
400
+ }
299
401
  }
300
402
  catch (err) {
301
403
  console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
@@ -384,24 +486,244 @@ export function registerSecretsCommand(program) {
384
486
  }
385
487
  const nameWidth = Math.max(4, ...data.secrets.map((s) => s.name.length));
386
488
  const hasPermission = data.secrets.some((s) => s.permission !== undefined);
489
+ const tierWidth = Math.max(4, ...data.secrets.map((s) => normalizeSecretTier(s.tier).length));
490
+ const scriptLockWidth = Math.max(11, ...data.secrets.map((s) => normalizeScriptLockMode(s.scriptLock?.mode).length));
387
491
  if (hasPermission) {
388
492
  const accessWidth = Math.max(6, ...data.secrets.map((s) => (s.permission ?? "-").length));
389
- const header = `${"NAME".padEnd(nameWidth)} ${"ACCESS".padEnd(accessWidth)} LAST MODIFIED`;
493
+ const header = `${"NAME".padEnd(nameWidth)} ${"ACCESS".padEnd(accessWidth)} ${"TIER".padEnd(tierWidth)} ${"SCRIPT LOCK".padEnd(scriptLockWidth)} LAST MODIFIED`;
390
494
  console.log(chalk.bold(header));
391
495
  for (const s of data.secrets) {
392
496
  const access = s.permission ?? "-";
497
+ const tier = normalizeSecretTier(s.tier);
498
+ const scriptLock = normalizeScriptLockMode(s.scriptLock?.mode);
393
499
  const modified = s.lastModifiedDate ?? "-";
394
- console.log(`${s.name.padEnd(nameWidth)} ${access.padEnd(accessWidth)} ${modified}`);
500
+ console.log(`${s.name.padEnd(nameWidth)} ${access.padEnd(accessWidth)} ${tier.padEnd(tierWidth)} ${scriptLock.padEnd(scriptLockWidth)} ${modified}`);
395
501
  }
396
502
  }
397
503
  else {
398
- const header = `${"NAME".padEnd(nameWidth)} LAST MODIFIED`;
504
+ const header = `${"NAME".padEnd(nameWidth)} ${"TIER".padEnd(tierWidth)} ${"SCRIPT LOCK".padEnd(scriptLockWidth)} LAST MODIFIED`;
399
505
  console.log(chalk.bold(header));
400
506
  for (const s of data.secrets) {
507
+ const tier = normalizeSecretTier(s.tier);
508
+ const scriptLock = normalizeScriptLockMode(s.scriptLock?.mode);
401
509
  const modified = s.lastModifiedDate ?? "-";
402
- console.log(`${s.name.padEnd(nameWidth)} ${modified}`);
510
+ console.log(`${s.name.padEnd(nameWidth)} ${tier.padEnd(tierWidth)} ${scriptLock.padEnd(scriptLockWidth)} ${modified}`);
511
+ }
512
+ }
513
+ }
514
+ catch (err) {
515
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
516
+ process.exit(1);
517
+ }
518
+ });
519
+ const policy = secrets
520
+ .command("policy")
521
+ .description("View or update secret access policy");
522
+ policy
523
+ .command("get <path>")
524
+ .description("Show the policy for a secret path")
525
+ .action(async (secretPath) => {
526
+ try {
527
+ rejectIfPersonal(secrets.opts(), "policy get");
528
+ if (!SECRET_NAME_PATTERN.test(secretPath)) {
529
+ console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
530
+ process.exit(1);
531
+ }
532
+ const token = await ensureCognitoToken();
533
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
534
+ const res = await vaultApiFetch({
535
+ token,
536
+ path: `/secrets/${encodeURIComponent(companyUid)}/policy`,
537
+ query: { path: secretPath },
538
+ });
539
+ if (res.status === 404) {
540
+ renderPolicySummary(normalizePolicyRecord(secretPath, { policy: { path: secretPath } }));
541
+ return;
542
+ }
543
+ if (!res.ok) {
544
+ const body = (await res.json().catch(() => ({})));
545
+ console.error(chalk.red(`Failed to get policy: ${extractApiMessage(body, res.statusText)}`));
546
+ process.exit(1);
547
+ }
548
+ const data = (await res.json());
549
+ renderPolicySummary(normalizePolicyRecord(secretPath, data));
550
+ }
551
+ catch (err) {
552
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
553
+ process.exit(1);
554
+ }
555
+ });
556
+ policy
557
+ .command("set <path>")
558
+ .description("Set the policy for a secret path")
559
+ .option("--tier <tier>", "Secret tier: standard | sensitive | nuclear")
560
+ .option("--lock-script <mode>", "Script-lock mode: off | enforce")
561
+ .action(async (secretPath, opts) => {
562
+ try {
563
+ rejectIfPersonal(secrets.opts(), "policy set");
564
+ if (!SECRET_NAME_PATTERN.test(secretPath)) {
565
+ console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
566
+ process.exit(1);
567
+ }
568
+ const tier = opts.tier;
569
+ if (tier !== undefined &&
570
+ tier !== "standard" &&
571
+ tier !== "sensitive" &&
572
+ tier !== "nuclear") {
573
+ console.error(chalk.red(`Invalid tier '${tier}': must be one of standard, sensitive, nuclear`));
574
+ process.exit(1);
575
+ }
576
+ let scriptLock;
577
+ if (opts.lockScript !== undefined) {
578
+ if (opts.lockScript === "off") {
579
+ scriptLock = { mode: "off" };
580
+ }
581
+ else if (opts.lockScript === "enforce") {
582
+ scriptLock = { mode: "enforced" };
583
+ }
584
+ else {
585
+ console.error(chalk.red(`Invalid script lock mode '${opts.lockScript}': must be one of off, enforce`));
586
+ process.exit(1);
403
587
  }
404
588
  }
589
+ if (tier === undefined && scriptLock === undefined) {
590
+ console.error(chalk.red("Error: provide at least one of --tier or --lock-script."));
591
+ process.exit(1);
592
+ }
593
+ const token = await ensureCognitoToken();
594
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
595
+ const res = await vaultApiFetch({
596
+ token,
597
+ path: `/secrets/${encodeURIComponent(companyUid)}/policy`,
598
+ method: "PUT",
599
+ body: {
600
+ path: secretPath,
601
+ ...(tier !== undefined ? { tier } : {}),
602
+ ...(scriptLock ? { scriptLock } : {}),
603
+ },
604
+ });
605
+ if (!res.ok) {
606
+ const body = (await res.json().catch(() => ({})));
607
+ console.error(chalk.red(`Failed to set policy: ${extractApiMessage(body, res.statusText)}`));
608
+ process.exit(1);
609
+ }
610
+ const data = (await res.json().catch(() => ({})));
611
+ console.log(chalk.green(`Policy updated for '${secretPath}'.`));
612
+ renderPolicySummary(normalizePolicyRecord(secretPath, data.policy ? data : { policy: { path: secretPath, tier, scriptLock } }));
613
+ }
614
+ catch (err) {
615
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
616
+ process.exit(1);
617
+ }
618
+ });
619
+ const script = secrets
620
+ .command("script")
621
+ .description("Manage approved scripts for script-locked secrets");
622
+ script
623
+ .command("approve <path>")
624
+ .description("Approve a script for a secret path")
625
+ .requiredOption("--id <scriptId>", "Stable script identifier")
626
+ .requiredOption("--script <path>", "Path to the local script file")
627
+ .option("--attestation <level>", "Attestation level", "self-asserted-hash")
628
+ .action(async (secretPath, opts) => {
629
+ try {
630
+ rejectIfPersonal(secrets.opts(), "script approve");
631
+ if (!SECRET_NAME_PATTERN.test(secretPath)) {
632
+ console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
633
+ process.exit(1);
634
+ }
635
+ const usage = await buildSecretUsage("exec", opts.script, opts.id, opts.attestation);
636
+ const token = await ensureCognitoToken();
637
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
638
+ const res = await vaultApiFetch({
639
+ token,
640
+ path: `/secrets/${encodeURIComponent(companyUid)}/policy/scripts`,
641
+ method: "POST",
642
+ body: {
643
+ path: secretPath,
644
+ scriptId: usage.script?.scriptId,
645
+ scriptPath: usage.script?.path,
646
+ sha256: usage.script?.sha256,
647
+ attestationLevel: usage.script?.attestationLevel,
648
+ },
649
+ });
650
+ if (!res.ok) {
651
+ const body = (await res.json().catch(() => ({})));
652
+ console.error(chalk.red(`Failed to approve script: ${extractApiMessage(body, res.statusText)}`));
653
+ process.exit(1);
654
+ }
655
+ console.log(chalk.green(`Approved script '${opts.id}' for '${secretPath}'.`));
656
+ }
657
+ catch (err) {
658
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
659
+ process.exit(1);
660
+ }
661
+ });
662
+ script
663
+ .command("revoke <path>")
664
+ .description("Revoke an approved script from a secret path")
665
+ .requiredOption("--id <scriptId>", "Stable script identifier")
666
+ .action(async (secretPath, opts) => {
667
+ try {
668
+ rejectIfPersonal(secrets.opts(), "script revoke");
669
+ if (!SECRET_NAME_PATTERN.test(secretPath)) {
670
+ console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
671
+ process.exit(1);
672
+ }
673
+ const token = await ensureCognitoToken();
674
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
675
+ const res = await vaultApiFetch({
676
+ token,
677
+ path: `/secrets/${encodeURIComponent(companyUid)}/policy/scripts`,
678
+ method: "DELETE",
679
+ body: { path: secretPath, scriptId: opts.id },
680
+ });
681
+ if (!res.ok) {
682
+ const body = (await res.json().catch(() => ({})));
683
+ console.error(chalk.red(`Failed to revoke script: ${extractApiMessage(body, res.statusText)}`));
684
+ process.exit(1);
685
+ }
686
+ console.log(chalk.green(`Revoked script '${opts.id}' for '${secretPath}'.`));
687
+ }
688
+ catch (err) {
689
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
690
+ process.exit(1);
691
+ }
692
+ });
693
+ script
694
+ .command("list <path>")
695
+ .description("List approved scripts for a secret path")
696
+ .action(async (secretPath) => {
697
+ try {
698
+ rejectIfPersonal(secrets.opts(), "script list");
699
+ if (!SECRET_NAME_PATTERN.test(secretPath)) {
700
+ console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
701
+ process.exit(1);
702
+ }
703
+ const token = await ensureCognitoToken();
704
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
705
+ const res = await vaultApiFetch({
706
+ token,
707
+ path: `/secrets/${encodeURIComponent(companyUid)}/policy`,
708
+ query: { path: secretPath },
709
+ });
710
+ if (res.status === 404) {
711
+ const policyRecord = normalizePolicyRecord(secretPath, {
712
+ policy: { path: secretPath },
713
+ });
714
+ renderPolicySummary(policyRecord);
715
+ renderPolicyScripts(policyRecord.scripts);
716
+ return;
717
+ }
718
+ if (!res.ok) {
719
+ const body = (await res.json().catch(() => ({})));
720
+ console.error(chalk.red(`Failed to list scripts: ${extractApiMessage(body, res.statusText)}`));
721
+ process.exit(1);
722
+ }
723
+ const data = (await res.json());
724
+ const policyRecord = normalizePolicyRecord(secretPath, data);
725
+ renderPolicySummary(policyRecord);
726
+ renderPolicyScripts(policyRecord.scripts);
405
727
  }
406
728
  catch (err) {
407
729
  console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
@@ -449,6 +771,7 @@ export function registerSecretsCommand(program) {
449
771
  .command("exec")
450
772
  .description("Run a command with secrets injected as env vars")
451
773
  .requiredOption("--only <keys>", "Comma-separated list of secret names to inject (required)")
774
+ .option("--script <path>", "Attach local script identity for script-locked secrets")
452
775
  .allowUnknownOption(true)
453
776
  .action(async (_opts, cmd) => {
454
777
  try {
@@ -478,7 +801,7 @@ export function registerSecretsCommand(program) {
478
801
  }
479
802
  const token = await ensureCognitoToken();
480
803
  const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
481
- const revealed = await loadRevealedSecrets(token, companyUid, keys);
804
+ const revealed = await loadRevealedSecrets(token, companyUid, keys, await buildSecretUsage("exec", _opts.script));
482
805
  const secretEnv = {};
483
806
  for (const key of keys) {
484
807
  const value = revealed.get(key);
@@ -514,6 +837,7 @@ export function registerSecretsCommand(program) {
514
837
  .command("env")
515
838
  .description("Print 'export KEY=VALUE' lines suitable for: source <(hq secrets env --only K1,K2)")
516
839
  .requiredOption("--only <keys>", "Comma-separated list of secret names to print (required)")
840
+ .option("--script <path>", "Attach local script identity for script-locked secrets")
517
841
  .action(async (opts) => {
518
842
  try {
519
843
  const redact = process.stdout.isTTY;
@@ -533,7 +857,7 @@ export function registerSecretsCommand(program) {
533
857
  }
534
858
  const token = await ensureCognitoToken();
535
859
  const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
536
- const revealed = await loadRevealedSecrets(token, companyUid, keys);
860
+ const revealed = await loadRevealedSecrets(token, companyUid, keys, await buildSecretUsage("env", opts.script));
537
861
  for (const key of keys) {
538
862
  const value = revealed.get(key);
539
863
  // loadRevealedSecrets throws on any unresolved key, so a miss here is
@@ -801,4 +1125,4 @@ export function registerSecretsCommand(program) {
801
1125
  });
802
1126
  }
803
1127
  //# sourceMappingURL=secrets.js.map
804
- //# debugId=bdef03fd-2f30-5034-8f32-23a5eb77cc3d
1128
+ //# debugId=d01cb1a1-b851-5ffe-9608-e505e55dcdc8
@@ -1,17 +1,9 @@
1
+ import type { SecretLoadResponse, SecretUsage } from '../commands/secrets.js';
1
2
  export interface InstallHqPluginOpts {
2
3
  companyOverride?: string;
3
4
  resolveCompanyUid: (slug: string) => Promise<string>;
4
- fetchBatch: (uid: string, names: string[]) => Promise<{
5
- secrets: Array<{
6
- name: string;
7
- value: string;
8
- }>;
9
- errors: Array<{
10
- name: string;
11
- code: string;
12
- message?: string;
13
- }>;
14
- }>;
5
+ usage?: SecretUsage;
6
+ fetchBatch: (uid: string, names: string[], usage?: SecretUsage) => Promise<SecretLoadResponse>;
15
7
  }
16
8
  export interface PluginState {
17
9
  schemaCompanySlug: string | null;
@@ -20,6 +12,7 @@ export interface PluginState {
20
12
  code: string;
21
13
  message?: string;
22
14
  }>;
15
+ loadedSecretsByName: Map<string, string>;
23
16
  }
24
17
  export declare function installHqPlugin(graph: any, opts: InstallHqPluginOpts): PluginState;
25
18
  export declare function prewarmHqSecrets(graph: any, opts: InstallHqPluginOpts, state: PluginState): Promise<void>;
@@ -1,12 +1,18 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="01fee323-1f63-5411-bfc9-ff49e241f712")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="63aeb21a-b1e7-5bff-8c7d-560fad3cc0dd")}catch(e){}}();
3
3
  import { ResolutionError } from 'varlock/plugin-lib';
4
- import { readCache, writeCache } from '../utils/secrets-cache.js';
4
+ import { DEFAULT_SECRETS_CACHE_TTL_MS, readCache, writeCache, } from '../utils/secrets-cache.js';
5
+ function normalizeCacheTtlMs(cacheTtlMs) {
6
+ return typeof cacheTtlMs === 'number'
7
+ ? cacheTtlMs
8
+ : DEFAULT_SECRETS_CACHE_TTL_MS;
9
+ }
5
10
  export function installHqPlugin(graph /* EnvGraph */, opts) {
6
11
  const pluginState = {
7
12
  schemaCompanySlug: null,
8
13
  uid: null,
9
14
  errorsByName: new Map(),
15
+ loadedSecretsByName: new Map(),
10
16
  };
11
17
  // varlock@1.0.0's plugin-lib.js omits the Resolver export (d.ts/JS mismatch);
12
18
  // extract it at runtime from any already-registered built-in resolver's prototype.
@@ -63,6 +69,10 @@ export function installHqPlugin(graph /* EnvGraph */, opts) {
63
69
  if (pluginState.uid == null) {
64
70
  throw new ResolutionError('Internal error: prewarmHqSecrets was not called before resolveEnvValues');
65
71
  }
72
+ const inMemory = pluginState.loadedSecretsByName.get(secretName);
73
+ if (inMemory != null) {
74
+ return inMemory;
75
+ }
66
76
  const cached = readCache(pluginState.uid, secretName); // string | null
67
77
  if (cached == null) {
68
78
  throw new ResolutionError(`Internal error: pre-warm did not populate cache for "${secretName}"`);
@@ -129,9 +139,16 @@ export async function prewarmHqSecrets(graph /* EnvGraph */, opts, state) {
129
139
  if (uniqueNames.length > 100) {
130
140
  throw new Error(`hq run supports at most 100 hq() resolvers per schema; got ${uniqueNames.length}`);
131
141
  }
132
- const result = await opts.fetchBatch(uid, uniqueNames);
142
+ const result = await opts.fetchBatch(uid, uniqueNames, opts.usage);
133
143
  for (const s of result.secrets) {
134
- writeCache(uid, s.name, s.value);
144
+ if (s.value == null) {
145
+ continue;
146
+ }
147
+ state.loadedSecretsByName.set(s.name, s.value);
148
+ const cacheTtlMs = normalizeCacheTtlMs(s.cacheTtlMs);
149
+ if (cacheTtlMs > 0) {
150
+ writeCache(uid, s.name, s.value, cacheTtlMs);
151
+ }
135
152
  }
136
153
  const errorsByName = new Map();
137
154
  for (const e of result.errors) {
@@ -141,4 +158,4 @@ export async function prewarmHqSecrets(graph /* EnvGraph */, opts, state) {
141
158
  state.uid = uid;
142
159
  }
143
160
  //# sourceMappingURL=hq-plugin.js.map
144
- //# debugId=01fee323-1f63-5411-bfc9-ff49e241f712
161
+ //# debugId=63aeb21a-b1e7-5bff-8c7d-560fad3cc0dd
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Integrity verification — SHA256 hash and RSA signature checks (US-005)
3
3
  */
4
+ /**
5
+ * Verify a file's SHA256 hash matches the expected value.
6
+ */
7
+ export declare function computeSha256(filePath: string): Promise<string>;
4
8
  /**
5
9
  * Verify a file's SHA256 hash matches the expected value.
6
10
  */
@@ -2,7 +2,7 @@
2
2
  * Integrity verification — SHA256 hash and RSA signature checks (US-005)
3
3
  */
4
4
 
5
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="820f12b3-4caf-5f08-9add-621628715744")}catch(e){}}();
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e47a47a7-a9cf-56f1-9522-983ebaa0e5cb")}catch(e){}}();
6
6
  import * as crypto from 'crypto';
7
7
  import * as fs from 'fs';
8
8
  import * as path from 'path';
@@ -10,18 +10,22 @@ import { resolveDefaultHqRoot } from './cognito-session.js';
10
10
  /**
11
11
  * Verify a file's SHA256 hash matches the expected value.
12
12
  */
13
- export async function verifySha256(filePath, expectedHash) {
13
+ export async function computeSha256(filePath) {
14
14
  return new Promise((resolve, reject) => {
15
15
  const hash = crypto.createHash('sha256');
16
16
  const stream = fs.createReadStream(filePath);
17
17
  stream.on('data', (chunk) => hash.update(chunk));
18
- stream.on('end', () => {
19
- const computed = hash.digest('hex');
20
- resolve(computed === expectedHash.toLowerCase());
21
- });
18
+ stream.on('end', () => resolve(hash.digest('hex')));
22
19
  stream.on('error', reject);
23
20
  });
24
21
  }
22
+ /**
23
+ * Verify a file's SHA256 hash matches the expected value.
24
+ */
25
+ export async function verifySha256(filePath, expectedHash) {
26
+ const computed = await computeSha256(filePath);
27
+ return computed === expectedHash.toLowerCase();
28
+ }
25
29
  /**
26
30
  * Verify an RSA signature of a SHA256 hash using the registry public key.
27
31
  * The public key is expected at packages/.keys/registry-public.pem.
@@ -40,4 +44,4 @@ export function verifyRsaSignature(sha256Hash, signature, publicKeyPath) {
40
44
  return verifier.verify(publicKey, Buffer.from(signature, 'base64'));
41
45
  }
42
46
  //# sourceMappingURL=integrity.js.map
43
- //# debugId=820f12b3-4caf-5f08-9add-621628715744
47
+ //# debugId=e47a47a7-a9cf-56f1-9522-983ebaa0e5cb
@@ -1,5 +1,6 @@
1
+ export declare const DEFAULT_SECRETS_CACHE_TTL_MS: number;
1
2
  export declare function readCache(companyUid: string, name: string): string | null;
2
- export declare function writeCache(companyUid: string, name: string, value: string): void;
3
+ export declare function writeCache(companyUid: string, name: string, value: string, ttlMs?: number): void;
3
4
  export declare function removeCacheEntry(companyUid: string, name: string): void;
4
5
  export declare function clearAllCache(): {
5
6
  removed: number;