@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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range scan: the lines a branch added between <base> and <head>, as
|
|
3
|
+
* `commit-secret-scan.sh pr-check` needs them.
|
|
4
|
+
*
|
|
5
|
+
* What is scanned:
|
|
6
|
+
* - The added lines of every non-merge commit in base..head, each against
|
|
7
|
+
* its own parent. A secret committed in one commit and deleted in a later
|
|
8
|
+
* one is still reported: it stays in the branch history.
|
|
9
|
+
* - For every merge commit in base..head, the lines that differ from all of
|
|
10
|
+
* its parents (git's combined diff, "++" lines): content the merge itself
|
|
11
|
+
* introduced, such as a conflict resolution. Lines a merge only carries
|
|
12
|
+
* over from one parent are either base content or scanned in that
|
|
13
|
+
* parent's own commits.
|
|
14
|
+
* - The added lines of the net diff merge-base(base, head)..head.
|
|
15
|
+
* - Binary files those diffs add or change: path rules, plus their bytes
|
|
16
|
+
* scanned as text up to MAX_BINARY_BYTES (larger ones are warned about).
|
|
17
|
+
* Lines that already existed on the base, and earlier versions of lines the
|
|
18
|
+
* branch did not touch, are not scanned.
|
|
19
|
+
*
|
|
20
|
+
* Config comes from the repo's .gitleaks.toml:
|
|
21
|
+
* - Allowlists: the exact-value exemptions committed on the base (the same
|
|
22
|
+
* contract as `scan secrets --repo --allowlist-rev`). A branch cannot
|
|
23
|
+
* exempt its own commits by adding an allowlist.
|
|
24
|
+
* - Rules: [[rules]] committed on the base, added to the built-in rules.
|
|
25
|
+
* Rules the branch adds or changes are not compiled or run: a branch-added
|
|
26
|
+
* regex could be written to backtrack for minutes (ReDoS), and the base
|
|
27
|
+
* copy has been reviewed.
|
|
28
|
+
* A rejected allowlist or rules section is warned about and dropped; the scan
|
|
29
|
+
* still runs with the built-in rules (stricter, never looser).
|
|
30
|
+
*
|
|
31
|
+
* Range errors: an unknown rev, a reversed range (head behind base) and a
|
|
32
|
+
* merge-base failure other than "no common history" exit 2. base == head is an
|
|
33
|
+
* empty range: a warning and no findings.
|
|
34
|
+
*/
|
|
35
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
36
|
+
import { scanFragment, scanPath } from "./detect.js";
|
|
37
|
+
import { InvalidAllowlistError, InvalidRepoRulesError, ScanConfigError, loadCommittedAllowlists, loadCommittedRules } from "./repo-config.js";
|
|
38
|
+
import { compiledRules } from "./rules.js";
|
|
39
|
+
import { ALLOWLIST_IGNORED_WARNING, parseUnifiedDiff, repoRoot } from "./staged.js";
|
|
40
|
+
export const RULES_IGNORED_WARNING = "HQ secret scan: warning: the repo .gitleaks.toml [[rules]] were rejected; scanning with the built-in rules only";
|
|
41
|
+
export const EMPTY_RANGE_WARNING = "HQ secret scan: warning: the range is empty (base and head are the same commit); nothing to scan";
|
|
42
|
+
/** Binary blobs larger than this are not read; the scan warns instead. */
|
|
43
|
+
export const MAX_BINARY_BYTES = 10 * 1024 * 1024;
|
|
44
|
+
const PATCH_ARGS = [
|
|
45
|
+
"--no-color",
|
|
46
|
+
"--no-ext-diff",
|
|
47
|
+
"--no-textconv",
|
|
48
|
+
"--unified=0",
|
|
49
|
+
"--diff-filter=ACMR",
|
|
50
|
+
"--src-prefix=a/",
|
|
51
|
+
"--dst-prefix=b/",
|
|
52
|
+
];
|
|
53
|
+
function git(root, args) {
|
|
54
|
+
return execFileSync("git", ["-c", "core.quotePath=false", ...args], {
|
|
55
|
+
cwd: root,
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
maxBuffer: 1024 * 1024 * 1024,
|
|
58
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function commit(root, rev, label) {
|
|
62
|
+
if (!rev || rev.startsWith("-"))
|
|
63
|
+
throw new ScanConfigError(`${label} ${JSON.stringify(rev)} is not a commit`);
|
|
64
|
+
try {
|
|
65
|
+
return git(root, ["rev-parse", "--verify", "-q", `${rev}^{commit}`]).trim();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw new ScanConfigError(`${label} ${JSON.stringify(rev)} is not a commit in ${root}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** `<base>..<head>` or `<base>` (head = HEAD). A three-dot range is rejected: the net diff is always taken from the merge base. */
|
|
72
|
+
export function parseRange(range) {
|
|
73
|
+
if (range.includes("..."))
|
|
74
|
+
throw new ScanConfigError(`--range takes <base>..<head> or <base>, not ${JSON.stringify(range)}`);
|
|
75
|
+
const i = range.indexOf("..");
|
|
76
|
+
if (i < 0)
|
|
77
|
+
return { base: range, head: "HEAD" };
|
|
78
|
+
return { base: range.slice(0, i), head: range.slice(i + 2) || "HEAD" };
|
|
79
|
+
}
|
|
80
|
+
function warnAndDrop(load, warning, kind) {
|
|
81
|
+
try {
|
|
82
|
+
return load();
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (!(err instanceof kind))
|
|
86
|
+
throw err;
|
|
87
|
+
process.stderr.write(`${warning}\n`);
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function rangeRules(root, base) {
|
|
92
|
+
const builtIn = compiledRules();
|
|
93
|
+
const repo = warnAndDrop(() => loadCommittedAllowlists(root, base), ALLOWLIST_IGNORED_WARNING, InvalidAllowlistError);
|
|
94
|
+
const extra = warnAndDrop(() => loadCommittedRules(root, base), RULES_IGNORED_WARNING, InvalidRepoRulesError);
|
|
95
|
+
return { ...builtIn, rules: [...builtIn.rules, ...extra], repo };
|
|
96
|
+
}
|
|
97
|
+
/** merge-base, "" when the two have no common history; any other git failure is an error. */
|
|
98
|
+
function mergeBase(root, base, head) {
|
|
99
|
+
const r = spawnSync("git", ["merge-base", base, head], { cwd: root, encoding: "utf8" });
|
|
100
|
+
if (r.status === 0)
|
|
101
|
+
return r.stdout.trim();
|
|
102
|
+
if (r.status === 1 && !r.stdout.trim() && !r.stderr.trim())
|
|
103
|
+
return "";
|
|
104
|
+
throw new ScanConfigError(`git merge-base failed: ${(r.stderr || String(r.error ?? "")).trim() || `exit ${r.status}`}`);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Lines a merge introduced, from `git diff-tree --cc` output: a line counts
|
|
108
|
+
* only when every parent column is "+", i.e. it is in none of the parents.
|
|
109
|
+
*/
|
|
110
|
+
export function parseCombinedDiff(diff) {
|
|
111
|
+
const fragments = [];
|
|
112
|
+
let file = null;
|
|
113
|
+
let parents = 0;
|
|
114
|
+
let nextLine = 0;
|
|
115
|
+
let cur = null;
|
|
116
|
+
const flush = () => {
|
|
117
|
+
if (file && cur && cur.lines.length)
|
|
118
|
+
fragments.push({ file, text: cur.lines.join("\n"), startLine: cur.startLine });
|
|
119
|
+
cur = null;
|
|
120
|
+
};
|
|
121
|
+
for (const raw of diff.split("\n")) {
|
|
122
|
+
if (raw.startsWith("diff --cc ") || raw.startsWith("diff --combined ")) {
|
|
123
|
+
flush();
|
|
124
|
+
file = raw.replace(/^diff --(?:cc|combined) /, "");
|
|
125
|
+
parents = 0;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (parents === 0 && raw.startsWith("+++ ")) {
|
|
129
|
+
const p = raw.slice(4);
|
|
130
|
+
file = p === "/dev/null" ? null : p.replace(/^b\//, "");
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const h = /^(@{3,}) .*?\+(\d+)(?:,\d+)? \1/.exec(raw);
|
|
134
|
+
if (h) {
|
|
135
|
+
flush();
|
|
136
|
+
parents = h[1].length - 1;
|
|
137
|
+
nextLine = Number(h[2]);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (parents === 0)
|
|
141
|
+
continue;
|
|
142
|
+
const cols = raw.slice(0, parents);
|
|
143
|
+
if (cols.length < parents) {
|
|
144
|
+
flush();
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (cols.includes("-")) {
|
|
148
|
+
flush();
|
|
149
|
+
continue;
|
|
150
|
+
} // a line removed from some parent: not in the result
|
|
151
|
+
if (/^\++$/.test(cols)) {
|
|
152
|
+
if (!cur)
|
|
153
|
+
cur = { lines: [], startLine: nextLine };
|
|
154
|
+
cur.lines.push(raw.slice(parents));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
flush();
|
|
158
|
+
}
|
|
159
|
+
nextLine++;
|
|
160
|
+
}
|
|
161
|
+
flush();
|
|
162
|
+
return fragments;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Binary files a merge commit changed relative to every parent. The combined
|
|
166
|
+
* diff only says "Binary files differ" for them, so their content is read
|
|
167
|
+
* from the merge's tree instead. Deleted files are left out.
|
|
168
|
+
*/
|
|
169
|
+
function mergeBinaryFiles(root, merge) {
|
|
170
|
+
const files = git(root, ["diff-tree", "-c", "--name-only", "--no-commit-id", "-r", merge]).split("\n").filter(Boolean);
|
|
171
|
+
return files.filter((file) => {
|
|
172
|
+
try {
|
|
173
|
+
// git's own binary test: a NUL byte in the first 8000 bytes of the blob.
|
|
174
|
+
const head = execFileSync("git", ["cat-file", "blob", `${merge}:${file}`], {
|
|
175
|
+
cwd: root, maxBuffer: MAX_BINARY_BYTES + 1024, stdio: ["ignore", "pipe", "ignore"],
|
|
176
|
+
}).subarray(0, 8000);
|
|
177
|
+
return head.includes(0);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
// Too large to read here: still a binary candidate, so binaryText warns about it.
|
|
181
|
+
if (err.code === "ENOBUFS")
|
|
182
|
+
return true;
|
|
183
|
+
return false; // deleted in the merge, or a submodule
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function patches(root, base, head) {
|
|
188
|
+
const out = [];
|
|
189
|
+
const mb = mergeBase(root, base, head);
|
|
190
|
+
// No common history: everything on head is new.
|
|
191
|
+
const net = mb ? git(root, ["diff", ...PATCH_ARGS, mb, head]) : git(root, ["show", "--format=", ...PATCH_ARGS, "--root", head]);
|
|
192
|
+
out.push({ rev: head, ...parseUnifiedDiff(net) });
|
|
193
|
+
const commits = git(root, ["rev-list", "--reverse", "--parents", `${base}..${head}`]).split("\n").filter(Boolean);
|
|
194
|
+
for (const line of commits) {
|
|
195
|
+
const [c, ...parents] = line.split(" ");
|
|
196
|
+
if (parents.length > 1) {
|
|
197
|
+
const cc = git(root, ["diff-tree", "--cc", "-p", "--no-commit-id", "--no-color", "--no-ext-diff", "--no-textconv", "--unified=0", "--src-prefix=a/", "--dst-prefix=b/", c]);
|
|
198
|
+
out.push({ commit: c, rev: c, fragments: parseCombinedDiff(cc), binaryFiles: mergeBinaryFiles(root, c) });
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
out.push({ commit: c, rev: c, ...parseUnifiedDiff(git(root, ["diff-tree", "-p", "--root", "--no-commit-id", ...PATCH_ARGS, c])) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
/** A binary blob's bytes as text (latin1 keeps every byte), or null when it is too large to read. */
|
|
207
|
+
function binaryText(root, rev, file) {
|
|
208
|
+
let size;
|
|
209
|
+
try {
|
|
210
|
+
size = Number(git(root, ["cat-file", "-s", `${rev}:${file}`]).trim());
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
if (!(size <= MAX_BINARY_BYTES))
|
|
216
|
+
return null;
|
|
217
|
+
const buf = execFileSync("git", ["cat-file", "blob", `${rev}:${file}`], { cwd: root, maxBuffer: MAX_BINARY_BYTES + 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
218
|
+
return buf.toString("latin1");
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Findings for the lines added on <head> since <base>. A finding from a
|
|
222
|
+
* commit that the net diff no longer shows is named `<file>@<commit12>`, so
|
|
223
|
+
* the reader can find the version that held it.
|
|
224
|
+
*/
|
|
225
|
+
export function scanRange(cwd, range) {
|
|
226
|
+
const root = repoRoot(cwd);
|
|
227
|
+
const parsed = parseRange(range);
|
|
228
|
+
const base = commit(root, parsed.base, "range base");
|
|
229
|
+
const head = commit(root, parsed.head, "range head");
|
|
230
|
+
if (base === head) {
|
|
231
|
+
process.stderr.write(`${EMPTY_RANGE_WARNING}\n`);
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
if (!git(root, ["rev-list", "-n", "1", `${base}..${head}`]).trim()) {
|
|
235
|
+
throw new ScanConfigError(`range ${JSON.stringify(range)} has no commits: head is behind base (reversed range?)`);
|
|
236
|
+
}
|
|
237
|
+
const rules = rangeRules(root, base);
|
|
238
|
+
const out = [];
|
|
239
|
+
const seen = new Set();
|
|
240
|
+
const tooLarge = new Set();
|
|
241
|
+
for (const patch of patches(root, base, head)) {
|
|
242
|
+
const found = [...patch.fragments.flatMap((f) => scanFragment(f, rules))];
|
|
243
|
+
for (const file of patch.binaryFiles) {
|
|
244
|
+
found.push(...scanPath(file, rules));
|
|
245
|
+
const text = binaryText(root, patch.rev, file);
|
|
246
|
+
if (text === null)
|
|
247
|
+
tooLarge.add(file);
|
|
248
|
+
else if (text)
|
|
249
|
+
found.push(...scanFragment({ file, text, startLine: 1 }, rules));
|
|
250
|
+
}
|
|
251
|
+
for (const f of found) {
|
|
252
|
+
// The same rule on the same file at the same line is one report. A
|
|
253
|
+
// repeat never hides the only finding, so a dirty range always fails.
|
|
254
|
+
const key = `${f.ruleId}\u0000${f.file}\u0000${f.line}`;
|
|
255
|
+
if (seen.has(key))
|
|
256
|
+
continue;
|
|
257
|
+
seen.add(key);
|
|
258
|
+
out.push(patch.commit ? { ...f, file: `${f.file}@${patch.commit.slice(0, 12)}` } : f);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const file of tooLarge) {
|
|
262
|
+
process.stderr.write(`HQ secret scan: warning: binary ${file} is larger than ${MAX_BINARY_BYTES} bytes; its content was not scanned\n`);
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=range.js.map
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* the scanned repo's own `.gitleaks.toml`, with gitleaks semantics for
|
|
4
4
|
* `paths`, `regexes`, `regexTarget`, `stopwords`, `commits`, `targetRules` and
|
|
5
5
|
* `condition`. They apply on top of the built-in rules and allowlists, to that
|
|
6
|
-
* repo only.
|
|
7
|
-
*
|
|
6
|
+
* repo only. `[extend]` and the rest of the file are ignored: a repo can allow
|
|
7
|
+
* known false positives, not switch built-in detection off. Repo `[[rules]]`
|
|
8
|
+
* are used by range scans only (`--range`, pr-check), and only add detection
|
|
9
|
+
* on top of the built-in rules (parseRepoRules below).
|
|
8
10
|
*
|
|
9
11
|
* The file is read from git, never the working tree:
|
|
10
12
|
* - The staged (index) copy applies only when it is identical to the copy
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
* scan exits 2.
|
|
34
36
|
*/
|
|
35
37
|
import { type Probe } from "./probes.js";
|
|
36
|
-
import { type CompiledAllowlist } from "./rules.js";
|
|
38
|
+
import { type CompiledAllowlist, type CompiledRule } from "./rules.js";
|
|
37
39
|
export declare class ScanConfigError extends Error {
|
|
38
40
|
}
|
|
39
41
|
/** The repo's .gitleaks.toml cannot be used; the scan ignores it (see the top of the file). */
|
|
@@ -68,4 +70,17 @@ export declare function loadRepoAllowlists(repoRoot: string): CompiledAllowlist[
|
|
|
68
70
|
* its own commits by adding an allowlist. An unknown rev is an error.
|
|
69
71
|
*/
|
|
70
72
|
export declare function loadCommittedAllowlists(repoRoot: string, rev?: string): CompiledAllowlist[];
|
|
73
|
+
/** The repo's [[rules]] cannot be used; a range scan warns and keeps the built-in rules only. */
|
|
74
|
+
export declare class InvalidRepoRulesError extends ScanConfigError {
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Extra detection rules from the `[[rules]]` of a .gitleaks.toml. They are
|
|
78
|
+
* added to the built-in rules and can only report more: a repo rule cannot
|
|
79
|
+
* replace or disable a built-in one (a built-in id is refused, `[extend]` and
|
|
80
|
+
* `useDefault` are ignored), and a rule's own allowlists apply to that rule
|
|
81
|
+
* only. [] when the file has no rules.
|
|
82
|
+
*/
|
|
83
|
+
export declare function parseRepoRules(text: string, file?: string): CompiledRule[];
|
|
84
|
+
/** Repo rules from the copy of .gitleaks.toml committed at `rev`. [] when there is none. */
|
|
85
|
+
export declare function loadCommittedRules(repoRoot: string, rev: string): CompiledRule[];
|
|
71
86
|
//# sourceMappingURL=repo-config.d.ts.map
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* the scanned repo's own `.gitleaks.toml`, with gitleaks semantics for
|
|
4
4
|
* `paths`, `regexes`, `regexTarget`, `stopwords`, `commits`, `targetRules` and
|
|
5
5
|
* `condition`. They apply on top of the built-in rules and allowlists, to that
|
|
6
|
-
* repo only.
|
|
7
|
-
*
|
|
6
|
+
* repo only. `[extend]` and the rest of the file are ignored: a repo can allow
|
|
7
|
+
* known false positives, not switch built-in detection off. Repo `[[rules]]`
|
|
8
|
+
* are used by range scans only (`--range`, pr-check), and only add detection
|
|
9
|
+
* on top of the built-in rules (parseRepoRules below).
|
|
8
10
|
*
|
|
9
11
|
* The file is read from git, never the working tree:
|
|
10
12
|
* - The staged (index) copy applies only when it is identical to the copy
|
|
@@ -361,6 +363,137 @@ export function loadCommittedAllowlists(repoRoot, rev = "HEAD") {
|
|
|
361
363
|
}
|
|
362
364
|
return revAllowlists(repoRoot, rev, parseExactAllowlists);
|
|
363
365
|
}
|
|
366
|
+
/** The repo's [[rules]] cannot be used; a range scan warns and keeps the built-in rules only. */
|
|
367
|
+
export class InvalidRepoRulesError extends ScanConfigError {
|
|
368
|
+
}
|
|
369
|
+
const RULE_KEYS = new Set(["id", "description", "regex", "path", "entropy", "secretGroup", "keywords", "allowlists", "tags"]);
|
|
370
|
+
const RULE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
371
|
+
/** Detection regexes are longer than allowlist ones; gitleaks' own rules run to a few hundred characters. */
|
|
372
|
+
const MAX_RULE_REGEX_LENGTH = 1024;
|
|
373
|
+
function failRules(file, where, msg) {
|
|
374
|
+
throw new InvalidRepoRulesError(`${file}: ${where}: ${msg}`);
|
|
375
|
+
}
|
|
376
|
+
function ruleRegex(file, where, key, src, flags = "") {
|
|
377
|
+
if (typeof src !== "string" || !src)
|
|
378
|
+
failRules(file, where, `${key} must be a non-empty string`);
|
|
379
|
+
if (src.length > MAX_RULE_REGEX_LENGTH)
|
|
380
|
+
failRules(file, where, `${key} is longer than ${MAX_RULE_REGEX_LENGTH} characters`);
|
|
381
|
+
if (NESTED_QUANTIFIER.test(src))
|
|
382
|
+
failRules(file, where, `${key} has a nested quantifier; simplify it`);
|
|
383
|
+
try {
|
|
384
|
+
return compileGoRegex(src, flags);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
failRules(file, where, `${key} is not a supported regex`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function ruleAllowlist(file, where, raw) {
|
|
391
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
392
|
+
failRules(file, where, "must be a table");
|
|
393
|
+
const t = raw;
|
|
394
|
+
for (const k of Object.keys(t))
|
|
395
|
+
if (!KEYS.has(k) || k === "targetRules")
|
|
396
|
+
failRules(file, where, `unknown key "${k}"`);
|
|
397
|
+
const list = (key) => {
|
|
398
|
+
const v = t[key];
|
|
399
|
+
if (v === undefined)
|
|
400
|
+
return undefined;
|
|
401
|
+
if (!Array.isArray(v) || v.some((x) => typeof x !== "string"))
|
|
402
|
+
failRules(file, where, `${key} must be a list of strings`);
|
|
403
|
+
return v;
|
|
404
|
+
};
|
|
405
|
+
const regexes = list("regexes");
|
|
406
|
+
const paths = list("paths");
|
|
407
|
+
regexes?.forEach((src, i) => ruleRegex(file, where, `regexes[${i}]`, src));
|
|
408
|
+
paths?.forEach((src, i) => ruleRegex(file, where, `paths[${i}]`, src));
|
|
409
|
+
const condition = typeof t.condition === "string" && t.condition.trim().toUpperCase() === "AND" ? "AND" : "OR";
|
|
410
|
+
const target = t.regexTarget;
|
|
411
|
+
if (target !== undefined && target !== "secret" && target !== "match" && target !== "line") {
|
|
412
|
+
failRules(file, where, 'regexTarget must be "secret", "match" or "line"');
|
|
413
|
+
}
|
|
414
|
+
return compileAllowlist({
|
|
415
|
+
condition,
|
|
416
|
+
regexTarget: target,
|
|
417
|
+
paths,
|
|
418
|
+
regexes,
|
|
419
|
+
stopwords: list("stopwords"),
|
|
420
|
+
commits: list("commits"),
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Extra detection rules from the `[[rules]]` of a .gitleaks.toml. They are
|
|
425
|
+
* added to the built-in rules and can only report more: a repo rule cannot
|
|
426
|
+
* replace or disable a built-in one (a built-in id is refused, `[extend]` and
|
|
427
|
+
* `useDefault` are ignored), and a rule's own allowlists apply to that rule
|
|
428
|
+
* only. [] when the file has no rules.
|
|
429
|
+
*/
|
|
430
|
+
export function parseRepoRules(text, file = REPO_CONFIG_FILE) {
|
|
431
|
+
let doc;
|
|
432
|
+
try {
|
|
433
|
+
doc = parse(text);
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
throw new InvalidRepoRulesError(`${file}: could not be parsed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
|
|
437
|
+
}
|
|
438
|
+
if (doc.rules === undefined)
|
|
439
|
+
return [];
|
|
440
|
+
if (!Array.isArray(doc.rules))
|
|
441
|
+
failRules(file, "rules", "must be an array of tables ([[rules]])");
|
|
442
|
+
const builtIn = new Set(compiledRules().rules.map((r) => r.id));
|
|
443
|
+
const seen = new Set();
|
|
444
|
+
return doc.rules.map((raw, i) => {
|
|
445
|
+
const where = `[[rules]] #${i + 1}`;
|
|
446
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
447
|
+
failRules(file, where, "must be a table");
|
|
448
|
+
const t = raw;
|
|
449
|
+
for (const k of Object.keys(t))
|
|
450
|
+
if (!RULE_KEYS.has(k))
|
|
451
|
+
failRules(file, where, `unknown key "${k}"`);
|
|
452
|
+
if (typeof t.id !== "string" || !RULE_ID.test(t.id))
|
|
453
|
+
failRules(file, where, "id must be 1-64 letters, digits, dots, dashes or underscores");
|
|
454
|
+
if (builtIn.has(t.id))
|
|
455
|
+
failRules(file, where, `id "${t.id}" is a built-in rule; a repo rule can add detection, not replace it`);
|
|
456
|
+
if (seen.has(t.id))
|
|
457
|
+
failRules(file, where, `id "${t.id}" is used twice`);
|
|
458
|
+
seen.add(t.id);
|
|
459
|
+
if (t.regex === undefined && t.path === undefined)
|
|
460
|
+
failRules(file, where, "needs regex or path");
|
|
461
|
+
if (t.description !== undefined && typeof t.description !== "string")
|
|
462
|
+
failRules(file, where, "description must be a string");
|
|
463
|
+
if (t.entropy !== undefined && (typeof t.entropy !== "number" || !(t.entropy >= 0)))
|
|
464
|
+
failRules(file, where, "entropy must be a number >= 0");
|
|
465
|
+
if (t.secretGroup !== undefined && (!Number.isInteger(t.secretGroup) || t.secretGroup < 0)) {
|
|
466
|
+
failRules(file, where, "secretGroup must be an integer >= 0");
|
|
467
|
+
}
|
|
468
|
+
if (t.keywords !== undefined && (!Array.isArray(t.keywords) || t.keywords.some((k) => typeof k !== "string" || !k))) {
|
|
469
|
+
failRules(file, where, "keywords must be a list of non-empty strings");
|
|
470
|
+
}
|
|
471
|
+
if (t.allowlists !== undefined && !Array.isArray(t.allowlists))
|
|
472
|
+
failRules(file, where, "allowlists must be an array of tables");
|
|
473
|
+
return {
|
|
474
|
+
id: t.id,
|
|
475
|
+
description: typeof t.description === "string" && t.description ? t.description : `Repo rule ${t.id}`,
|
|
476
|
+
regex: t.regex === undefined ? undefined : ruleRegex(file, where, "regex", t.regex, "g"),
|
|
477
|
+
path: t.path === undefined ? undefined : ruleRegex(file, where, "path", t.path),
|
|
478
|
+
entropy: t.entropy,
|
|
479
|
+
secretGroup: t.secretGroup,
|
|
480
|
+
keywords: (t.keywords ?? []).map((k) => k.toLowerCase()),
|
|
481
|
+
allowlists: (t.allowlists ?? []).map((a, j) => ruleAllowlist(file, `${where} allowlists #${j + 1}`, a)),
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/** Repo rules from the copy of .gitleaks.toml committed at `rev`. [] when there is none. */
|
|
486
|
+
export function loadCommittedRules(repoRoot, rev) {
|
|
487
|
+
const e = entry(git(repoRoot, ["ls-tree", rev, "--", REPO_CONFIG_FILE]), 2);
|
|
488
|
+
if (!e)
|
|
489
|
+
return [];
|
|
490
|
+
if (e.mode === "120000")
|
|
491
|
+
throw new InvalidRepoRulesError(`${REPO_CONFIG_FILE} (${rev}) is a symlink; commit a regular file`);
|
|
492
|
+
const text = git(repoRoot, ["cat-file", "blob", e.blob]);
|
|
493
|
+
if (text === null)
|
|
494
|
+
throw new InvalidRepoRulesError(`${REPO_CONFIG_FILE} (${rev}) could not be read from git`);
|
|
495
|
+
return parseRepoRules(text, `${REPO_CONFIG_FILE} (${rev})`);
|
|
496
|
+
}
|
|
364
497
|
function headAllowlists(repoRoot, parser = parseRepoAllowlists) {
|
|
365
498
|
if (git(repoRoot, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]) === null)
|
|
366
499
|
return [];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict env-schema reader for `hq run --script` (feedback_0e6abcb8).
|
|
3
|
+
*
|
|
4
|
+
* With --script, `hq run` must not load the caller's schema through varlock:
|
|
5
|
+
* a varlock graph can run `exec()` resolvers, load plugins, or import other
|
|
6
|
+
* files, and any of that runs inside the CLI process while the fetched
|
|
7
|
+
* secrets are in memory. A caller could write `LEAK=exec(...)` next to a
|
|
8
|
+
* script-locked `hq()` item and read the secret before the script binding is
|
|
9
|
+
* checked.
|
|
10
|
+
*
|
|
11
|
+
* So the --script path reads the schema and local files with this parser and
|
|
12
|
+
* never builds a varlock graph. Only these lines are accepted (allowlist):
|
|
13
|
+
* - blank lines and `#` comments. A comment may carry only the decorators
|
|
14
|
+
* listed in ALLOWED_DECORATORS; any other `@decorator` is refused.
|
|
15
|
+
* - `KEY=hq()` or `KEY=hq("NAME")` / `KEY=hq('NAME')`: an HQ secret.
|
|
16
|
+
* - `KEY=literal`, optionally quoted, with no `(`, `)`, `$`, `{`, `}` or
|
|
17
|
+
* backtick: a static value. An empty value is allowed.
|
|
18
|
+
* Everything else is refused with an error before any secret is fetched.
|
|
19
|
+
* Later files override earlier ones, as in the varlock path.
|
|
20
|
+
*/
|
|
21
|
+
export interface StrictSchema {
|
|
22
|
+
statics: Record<string, string>;
|
|
23
|
+
/** Env key -> vault secret name. */
|
|
24
|
+
secrets: Map<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export declare function parseStrictSchemaFiles(paths: string[]): StrictSchema;
|
|
27
|
+
//# sourceMappingURL=script-bound-schema.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict env-schema reader for `hq run --script` (feedback_0e6abcb8).
|
|
3
|
+
*
|
|
4
|
+
* With --script, `hq run` must not load the caller's schema through varlock:
|
|
5
|
+
* a varlock graph can run `exec()` resolvers, load plugins, or import other
|
|
6
|
+
* files, and any of that runs inside the CLI process while the fetched
|
|
7
|
+
* secrets are in memory. A caller could write `LEAK=exec(...)` next to a
|
|
8
|
+
* script-locked `hq()` item and read the secret before the script binding is
|
|
9
|
+
* checked.
|
|
10
|
+
*
|
|
11
|
+
* So the --script path reads the schema and local files with this parser and
|
|
12
|
+
* never builds a varlock graph. Only these lines are accepted (allowlist):
|
|
13
|
+
* - blank lines and `#` comments. A comment may carry only the decorators
|
|
14
|
+
* listed in ALLOWED_DECORATORS; any other `@decorator` is refused.
|
|
15
|
+
* - `KEY=hq()` or `KEY=hq("NAME")` / `KEY=hq('NAME')`: an HQ secret.
|
|
16
|
+
* - `KEY=literal`, optionally quoted, with no `(`, `)`, `$`, `{`, `}` or
|
|
17
|
+
* backtick: a static value. An empty value is allowed.
|
|
18
|
+
* Everything else is refused with an error before any secret is fetched.
|
|
19
|
+
* Later files override earlier ones, as in the varlock path.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
const KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
23
|
+
const SECRET_NAME = /^[A-Z][A-Z0-9_]*(\/[A-Z][A-Z0-9_]+)*$/;
|
|
24
|
+
const HQ_CALL = /^hq\(\s*(?:(["'])([^"']*)\1)?\s*\)$/;
|
|
25
|
+
const UNSAFE_LITERAL = /[()${}`]/;
|
|
26
|
+
const ALLOWED_DECORATORS = new Set([
|
|
27
|
+
'hqCompany',
|
|
28
|
+
'required',
|
|
29
|
+
'optional',
|
|
30
|
+
'sensitive',
|
|
31
|
+
'public',
|
|
32
|
+
'defaultRequired',
|
|
33
|
+
'defaultSensitive',
|
|
34
|
+
]);
|
|
35
|
+
function refuse(file, lineNo, reason) {
|
|
36
|
+
throw new Error(`hq run --script reads ${file} in strict mode and refused line ${lineNo}: ${reason}. ` +
|
|
37
|
+
'With --script only KEY=hq(...), KEY=literal and the decorators ' +
|
|
38
|
+
`${[...ALLOWED_DECORATORS].map((d) => '@' + d).join(', ')} are allowed; resolvers such as exec() and @plugin/@import are not. ` +
|
|
39
|
+
'Remove them, or run without --script.');
|
|
40
|
+
}
|
|
41
|
+
function unquote(value) {
|
|
42
|
+
if (value.length >= 2) {
|
|
43
|
+
const q = value[0];
|
|
44
|
+
if ((q === '"' || q === "'") && value[value.length - 1] === q) {
|
|
45
|
+
const inner = value.slice(1, -1);
|
|
46
|
+
if (!inner.includes(q))
|
|
47
|
+
return inner;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
export function parseStrictSchemaFiles(paths) {
|
|
53
|
+
const statics = {};
|
|
54
|
+
const secrets = new Map();
|
|
55
|
+
for (const file of paths) {
|
|
56
|
+
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
|
|
57
|
+
lines.forEach((raw, index) => {
|
|
58
|
+
const lineNo = index + 1;
|
|
59
|
+
const line = raw.trim();
|
|
60
|
+
if (line === '')
|
|
61
|
+
return;
|
|
62
|
+
if (line.startsWith('#')) {
|
|
63
|
+
for (const match of line.matchAll(/@([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
64
|
+
if (!ALLOWED_DECORATORS.has(match[1]))
|
|
65
|
+
refuse(file, lineNo, `decorator @${match[1]} is not allowed`);
|
|
66
|
+
}
|
|
67
|
+
// An allowed decorator may take only a quoted literal argument.
|
|
68
|
+
const stripped = line.replace(/@hqCompany\(\s*"[^"()$`{}]*"\s*\)/g, '');
|
|
69
|
+
if (/[()]/.test(stripped) && /@/.test(stripped))
|
|
70
|
+
refuse(file, lineNo, 'decorator arguments are not allowed');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const eq = line.indexOf('=');
|
|
74
|
+
if (eq <= 0)
|
|
75
|
+
refuse(file, lineNo, 'expected KEY=value');
|
|
76
|
+
const key = line.slice(0, eq).trim().replace(/^export\s+/, '');
|
|
77
|
+
if (!KEY.test(key))
|
|
78
|
+
refuse(file, lineNo, 'invalid key');
|
|
79
|
+
const value = line.slice(eq + 1).trim();
|
|
80
|
+
const hq = HQ_CALL.exec(value);
|
|
81
|
+
if (hq) {
|
|
82
|
+
const name = hq[2] || key;
|
|
83
|
+
if (!SECRET_NAME.test(name))
|
|
84
|
+
refuse(file, lineNo, `invalid secret name for ${key}`);
|
|
85
|
+
secrets.set(key, name);
|
|
86
|
+
delete statics[key];
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (UNSAFE_LITERAL.test(value))
|
|
90
|
+
refuse(file, lineNo, `${key} is not a plain literal or hq() call`);
|
|
91
|
+
statics[key] = unquote(value);
|
|
92
|
+
secrets.delete(key);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return { statics, secrets };
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=script-bound-schema.js.map
|
|
@@ -76,6 +76,8 @@ export declare function requestedApiKeyCompany(flagCompany: string | undefined):
|
|
|
76
76
|
export declare function assertApiKeyCompany(token: string, requested: string, commandLabel: string): Promise<string>;
|
|
77
77
|
export declare function loadRevealedSecrets(token: string, companyUid: string, keys: string[], usage?: SecretUsage, options?: {
|
|
78
78
|
allowOfflineFallback?: boolean;
|
|
79
|
+
/** Filled with each returned row's scriptLock.mode (undefined when absent). */
|
|
80
|
+
lockModes?: Map<string, string | undefined>;
|
|
79
81
|
}): Promise<Map<string, string>>;
|
|
80
82
|
/**
|
|
81
83
|
* Emit `hq secrets env` output and terminate deterministically.
|
|
@@ -129,7 +129,7 @@ export function parseSecretNameList(input) {
|
|
|
129
129
|
// refuse a key bound to a different company. Without it the fetch route reads
|
|
130
130
|
// from the key's own company, which made `--company <other>` silently return
|
|
131
131
|
// the key company's same-named secret (P0 triage, 2026-10-11).
|
|
132
|
-
async function loadRevealedSecretsViaApiKey(token, keys, companyUid) {
|
|
132
|
+
async function loadRevealedSecretsViaApiKey(token, keys, companyUid, lockModes) {
|
|
133
133
|
const resolved = new Map();
|
|
134
134
|
const requested = [...new Set(keys)];
|
|
135
135
|
const cacheScope = "__api_key__";
|
|
@@ -170,6 +170,10 @@ async function loadRevealedSecretsViaApiKey(token, keys, companyUid) {
|
|
|
170
170
|
throw new Error(`Failed to fetch secret '${name}': malformed fetch response`);
|
|
171
171
|
}
|
|
172
172
|
removeCacheEntry(cacheScope, name);
|
|
173
|
+
// The API-key fetch route returns the secret's scriptLock (the vault denies
|
|
174
|
+
// an enforced secret on this route, since it carries no script usage).
|
|
175
|
+
const mode = secret.scriptLock?.mode;
|
|
176
|
+
lockModes?.set(name, typeof mode === "string" ? mode : undefined);
|
|
173
177
|
resolved.set(name, secret.value);
|
|
174
178
|
}
|
|
175
179
|
return resolved;
|
|
@@ -219,7 +223,7 @@ export async function assertApiKeyCompany(token, requested, commandLabel) {
|
|
|
219
223
|
}
|
|
220
224
|
export async function loadRevealedSecrets(token, companyUid, keys, usage, options = {}) {
|
|
221
225
|
if (token.startsWith(HQ_API_KEY_PREFIX)) {
|
|
222
|
-
return loadRevealedSecretsViaApiKey(token, keys, companyUid);
|
|
226
|
+
return loadRevealedSecretsViaApiKey(token, keys, companyUid, options.lockModes);
|
|
223
227
|
}
|
|
224
228
|
const resolved = new Map();
|
|
225
229
|
const requested = [...new Set(keys)];
|
|
@@ -284,6 +288,7 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage, option
|
|
|
284
288
|
throw new Error("Invalid secret load response from vault");
|
|
285
289
|
}
|
|
286
290
|
seenNames.add(s.name);
|
|
291
|
+
options.lockModes?.set(s.name, s.scriptLock?.mode);
|
|
287
292
|
if (s.revokedAt != null) {
|
|
288
293
|
errorsByName.set(s.name, {
|
|
289
294
|
code: "revoked",
|