@indigoai-us/hq-cli 5.345.163 → 5.345.165
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 +36 -0
- package/dist/command-catalog.generated.d.ts +66 -0
- package/dist/command-catalog.generated.js +82 -0
- package/dist/commands/billing-sponsor.d.ts +20 -0
- package/dist/commands/billing-sponsor.js +116 -0
- package/dist/commands/billing-usage.d.ts +16 -2
- package/dist/commands/billing-usage.js +63 -9
- package/dist/commands/billing.d.ts +34 -0
- package/dist/commands/billing.js +108 -1
- package/dist/commands/deploy-domains.d.ts +2 -0
- package/dist/commands/deploy-domains.js +18 -3
- package/dist/commands/scan.d.ts +6 -0
- package/dist/commands/scan.js +11 -1
- package/dist/lib/billing/billing-status-lines.d.ts +14 -0
- package/dist/lib/billing/billing-status-lines.js +43 -0
- package/dist/lib/billing/usage-only.d.ts +18 -0
- package/dist/lib/billing/usage-only.js +18 -1
- package/dist/lib/doctor/checks/sync-runtime.d.ts +34 -0
- package/dist/lib/doctor/checks/sync-runtime.js +217 -8
- package/dist/lib/doctor/fix/apply.js +1 -0
- package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +4 -1
- package/dist/lib/doctor/fix/refresh-sync-outcome.js +15 -0
- package/dist/lib/doctor/fix/refresh-sync-progress.d.ts +42 -0
- package/dist/lib/doctor/fix/refresh-sync-progress.js +100 -0
- package/dist/lib/doctor/json-output.d.ts +7 -0
- package/dist/lib/doctor/json-output.js +8 -1
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/work-subscribe.js +121 -38
- package/dist/lib/mesh/live/paths.d.ts +1 -0
- package/dist/lib/mesh/live/paths.js +1 -0
- package/dist/lib/secret-scan/detect.js +2 -0
- package/dist/lib/secret-scan/probes.d.ts +13 -0
- package/dist/lib/secret-scan/probes.js +69 -0
- package/dist/lib/secret-scan/repo-config.d.ts +24 -0
- package/dist/lib/secret-scan/repo-config.js +159 -29
- package/dist/lib/secret-scan/rules.d.ts +2 -0
- package/dist/lib/secret-scan/staged.d.ts +15 -1
- package/dist/lib/secret-scan/staged.js +46 -4
- package/package.json +7 -7
|
@@ -4,46 +4,67 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
7
8
|
import { createPersonalRealtimeFetcher, } from "./credentials.js";
|
|
8
9
|
import { presignIotWssUrl } from "./presign.js";
|
|
9
10
|
import { applyProjectViewPush, parseProjectViewPush } from "./live-cache.js";
|
|
10
11
|
import { backoffDelayMs, RECONNECT_MIN_DELAY_MS } from "./presence.js";
|
|
11
|
-
import { AGENT_INBOX_PUSH_STATE_BASENAME } from "../paths.js";
|
|
12
|
-
export { AGENT_INBOX_PUSH_STATE_BASENAME } from "../paths.js";
|
|
12
|
+
import { AGENT_INBOX_PUSH_STATE_BASENAME, SYNC_WAKE_STATE_BASENAME } from "../paths.js";
|
|
13
|
+
export { AGENT_INBOX_PUSH_STATE_BASENAME, SYNC_WAKE_STATE_BASENAME } from "../paths.js";
|
|
13
14
|
const INBOX_PUSH_HEARTBEAT_MS = 5_000;
|
|
14
|
-
function
|
|
15
|
-
const target = path.join(meshRoot,
|
|
15
|
+
function readWakeSequence(meshRoot, basename, label, onInfo) {
|
|
16
|
+
const target = path.join(meshRoot, basename);
|
|
16
17
|
try {
|
|
17
18
|
const info = fs.lstatSync(target);
|
|
18
19
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
19
|
-
onInfo?.(
|
|
20
|
-
return 0;
|
|
20
|
+
onInfo?.(`${label} state read ignored (not a regular file)`);
|
|
21
|
+
return { sequence: 0, writable: false };
|
|
21
22
|
}
|
|
22
23
|
const parsed = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
23
24
|
if (Number.isSafeInteger(parsed.wakeSequence) && Number(parsed.wakeSequence) >= 0) {
|
|
24
|
-
return Number(parsed.wakeSequence);
|
|
25
|
+
return { sequence: Number(parsed.wakeSequence), writable: true };
|
|
25
26
|
}
|
|
26
|
-
onInfo?.(
|
|
27
|
-
return 0;
|
|
27
|
+
onInfo?.(`${label} state read ignored (invalid wake sequence)`);
|
|
28
|
+
return { sequence: 0, writable: false };
|
|
28
29
|
}
|
|
29
30
|
catch (error) {
|
|
30
31
|
if (error.code !== "ENOENT") {
|
|
31
32
|
const name = error instanceof Error ? error.name : "UnknownError";
|
|
32
|
-
onInfo?.(
|
|
33
|
+
onInfo?.(`${label} state read failed (${name})`);
|
|
33
34
|
}
|
|
34
|
-
return 0;
|
|
35
|
+
return { sequence: 0, writable: error.code === "ENOENT" };
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
function
|
|
38
|
+
function writeWakeState(meshRoot, basename, state, durable) {
|
|
38
39
|
fs.mkdirSync(meshRoot, { recursive: true, mode: 0o700 });
|
|
39
|
-
const
|
|
40
|
-
|
|
40
|
+
const rootInfo = fs.lstatSync(meshRoot);
|
|
41
|
+
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) {
|
|
42
|
+
throw new Error("wake state root is not a regular directory");
|
|
43
|
+
}
|
|
44
|
+
const target = path.join(meshRoot, basename);
|
|
45
|
+
const temporary = path.join(meshRoot, `.${basename}.${process.pid}.${randomUUID()}.tmp`);
|
|
46
|
+
let file;
|
|
41
47
|
try {
|
|
42
|
-
fs.
|
|
43
|
-
fs.
|
|
48
|
+
file = fs.openSync(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600);
|
|
49
|
+
fs.writeFileSync(file, `${JSON.stringify(state)}\n`);
|
|
50
|
+
if (durable)
|
|
51
|
+
fs.fsyncSync(file);
|
|
52
|
+
fs.closeSync(file);
|
|
53
|
+
file = undefined;
|
|
44
54
|
fs.renameSync(temporary, target);
|
|
55
|
+
if (durable && process.platform !== "win32") {
|
|
56
|
+
const directory = fs.openSync(meshRoot, fs.constants.O_RDONLY);
|
|
57
|
+
try {
|
|
58
|
+
fs.fsyncSync(directory);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
fs.closeSync(directory);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
45
64
|
}
|
|
46
65
|
catch (error) {
|
|
66
|
+
if (file !== undefined)
|
|
67
|
+
fs.closeSync(file);
|
|
47
68
|
try {
|
|
48
69
|
fs.unlinkSync(temporary);
|
|
49
70
|
}
|
|
@@ -79,37 +100,46 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
79
100
|
let heartbeatTimer = null;
|
|
80
101
|
let generation = 0;
|
|
81
102
|
let inboxSubscribed = false;
|
|
82
|
-
let
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
let syncSubscribed = false;
|
|
104
|
+
let actorUid;
|
|
105
|
+
let inboxWakeSequence = readWakeSequence(opts.meshRoot, AGENT_INBOX_PUSH_STATE_BASENAME, "agent inbox push", opts.onInfo).sequence;
|
|
106
|
+
const syncWakeState = readWakeSequence(opts.meshRoot, SYNC_WAKE_STATE_BASENAME, "sync wake", opts.onInfo);
|
|
107
|
+
let syncWakeSequence = syncWakeState.sequence;
|
|
108
|
+
const syncStateWritable = syncWakeState.writable;
|
|
86
109
|
const clearInboxHeartbeat = () => {
|
|
87
110
|
if (heartbeatTimer !== null)
|
|
88
111
|
timers.clearTimeout(heartbeatTimer);
|
|
89
112
|
heartbeatTimer = null;
|
|
90
113
|
};
|
|
91
|
-
const
|
|
114
|
+
const persistState = (basename, label, connected, wakeSequence, identity, durable = true) => {
|
|
92
115
|
try {
|
|
93
|
-
|
|
116
|
+
writeWakeState(opts.meshRoot, basename, {
|
|
94
117
|
connected,
|
|
95
118
|
heartbeatAtMs: now().getTime(),
|
|
96
119
|
wakeSequence,
|
|
97
|
-
...(connected &&
|
|
98
|
-
});
|
|
120
|
+
...(connected && actorUid && identity ? { [identity]: actorUid } : {}),
|
|
121
|
+
}, durable);
|
|
99
122
|
}
|
|
100
123
|
catch (error) {
|
|
101
124
|
const name = error instanceof Error ? error.name : "UnknownError";
|
|
102
|
-
opts.onInfo?.(
|
|
125
|
+
opts.onInfo?.(`${label} state write failed (${name})`);
|
|
103
126
|
}
|
|
104
127
|
};
|
|
128
|
+
const persistInboxState = (connected, durable = true) => persistState(AGENT_INBOX_PUSH_STATE_BASENAME, "agent inbox push", connected, inboxWakeSequence, "agentUid", durable);
|
|
129
|
+
const persistSyncState = (connected, durable = true) => syncStateWritable
|
|
130
|
+
? persistState(SYNC_WAKE_STATE_BASENAME, "sync wake", connected, syncWakeSequence, undefined, durable)
|
|
131
|
+
: undefined;
|
|
105
132
|
const scheduleInboxHeartbeat = () => {
|
|
106
|
-
if (stopped || !inboxSubscribed)
|
|
133
|
+
if (stopped || (!inboxSubscribed && !syncSubscribed))
|
|
107
134
|
return;
|
|
108
135
|
clearInboxHeartbeat();
|
|
109
136
|
heartbeatTimer = timers.setTimeout(() => {
|
|
110
137
|
heartbeatTimer = null;
|
|
111
|
-
if (!stopped && inboxSubscribed) {
|
|
112
|
-
|
|
138
|
+
if (!stopped && (inboxSubscribed || syncSubscribed)) {
|
|
139
|
+
if (inboxSubscribed)
|
|
140
|
+
persistInboxState(true, false);
|
|
141
|
+
if (syncSubscribed)
|
|
142
|
+
persistSyncState(true, false);
|
|
113
143
|
scheduleInboxHeartbeat();
|
|
114
144
|
}
|
|
115
145
|
}, INBOX_PUSH_HEARTBEAT_MS);
|
|
@@ -118,9 +148,11 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
118
148
|
if (stopped)
|
|
119
149
|
return;
|
|
120
150
|
inboxSubscribed = false;
|
|
121
|
-
|
|
151
|
+
syncSubscribed = false;
|
|
152
|
+
actorUid = undefined;
|
|
122
153
|
clearInboxHeartbeat();
|
|
123
154
|
persistInboxState(false);
|
|
155
|
+
persistSyncState(false);
|
|
124
156
|
const gen = ++generation;
|
|
125
157
|
let bundle;
|
|
126
158
|
try {
|
|
@@ -137,14 +169,15 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
137
169
|
bundle.topics.inbox === `hq/${bundle.actorUid}/inbox`
|
|
138
170
|
? bundle.topics.inbox
|
|
139
171
|
: undefined;
|
|
140
|
-
|
|
141
|
-
|
|
172
|
+
const syncTopic = syncStateWritable ? `hq/${bundle.actorUid}/sync` : undefined;
|
|
173
|
+
actorUid = inboxTopic || syncTopic ? bundle.actorUid : undefined;
|
|
174
|
+
if (!topic && !inboxTopic && !syncTopic) {
|
|
142
175
|
opts.onInfo?.("work cache subscribe skipped: credentials have no supported topic");
|
|
143
176
|
return;
|
|
144
177
|
}
|
|
145
178
|
const url = presignIotWssUrl(bundle.credentials, bundle.iotEndpoint, bundle.region, now());
|
|
146
179
|
const next = opts.mqttConnect(url, {
|
|
147
|
-
clientId:
|
|
180
|
+
clientId: bundle.clientId,
|
|
148
181
|
clean: true,
|
|
149
182
|
reconnectPeriod: 0,
|
|
150
183
|
protocolVersion: 4,
|
|
@@ -154,7 +187,8 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
154
187
|
if (stopped || gen !== generation)
|
|
155
188
|
return;
|
|
156
189
|
reconnectLogged = false;
|
|
157
|
-
let pendingSubscriptions = Number(Boolean(topic)) + Number(Boolean(inboxTopic))
|
|
190
|
+
let pendingSubscriptions = Number(Boolean(topic)) + Number(Boolean(inboxTopic)) +
|
|
191
|
+
Number(Boolean(syncTopic));
|
|
158
192
|
let subscriptionFailed = false;
|
|
159
193
|
const finishSubscription = (err) => {
|
|
160
194
|
if (err) {
|
|
@@ -171,6 +205,13 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
171
205
|
if (pendingSubscriptions === 0)
|
|
172
206
|
attempt = 0;
|
|
173
207
|
};
|
|
208
|
+
const finishOptionalSubscription = () => {
|
|
209
|
+
if (subscriptionFailed)
|
|
210
|
+
return;
|
|
211
|
+
pendingSubscriptions -= 1;
|
|
212
|
+
if (pendingSubscriptions === 0)
|
|
213
|
+
attempt = 0;
|
|
214
|
+
};
|
|
174
215
|
if (topic) {
|
|
175
216
|
next.subscribe(topic, { qos: 1 }, (err) => finishSubscription(err));
|
|
176
217
|
}
|
|
@@ -180,7 +221,6 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
180
221
|
return;
|
|
181
222
|
if (err) {
|
|
182
223
|
inboxSubscribed = false;
|
|
183
|
-
inboxAgentUid = undefined;
|
|
184
224
|
clearInboxHeartbeat();
|
|
185
225
|
persistInboxState(false);
|
|
186
226
|
opts.onInfo?.("agent inbox push subscribe failed");
|
|
@@ -193,17 +233,37 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
193
233
|
finishSubscription();
|
|
194
234
|
});
|
|
195
235
|
}
|
|
236
|
+
if (syncTopic) {
|
|
237
|
+
next.subscribe(syncTopic, { qos: 1 }, (err) => {
|
|
238
|
+
if (stopped || gen !== generation)
|
|
239
|
+
return;
|
|
240
|
+
if (err) {
|
|
241
|
+
syncSubscribed = false;
|
|
242
|
+
persistSyncState(false);
|
|
243
|
+
opts.onInfo?.("sync wake subscribe failed");
|
|
244
|
+
finishOptionalSubscription();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
syncSubscribed = true;
|
|
248
|
+
persistSyncState(true);
|
|
249
|
+
scheduleInboxHeartbeat();
|
|
250
|
+
finishSubscription();
|
|
251
|
+
});
|
|
252
|
+
}
|
|
196
253
|
});
|
|
197
254
|
next.on("message", (_topic, payload) => {
|
|
198
255
|
if (stopped || gen !== generation)
|
|
199
256
|
return;
|
|
200
257
|
if (inboxTopic && _topic === inboxTopic && inboxSubscribed) {
|
|
201
|
-
|
|
202
|
-
// content; the authenticated inbox GET remains the source of truth.
|
|
203
|
-
wakeSequence += 1;
|
|
258
|
+
inboxWakeSequence += 1;
|
|
204
259
|
persistInboxState(true);
|
|
205
260
|
return;
|
|
206
261
|
}
|
|
262
|
+
if (syncTopic && _topic === syncTopic && syncSubscribed) {
|
|
263
|
+
syncWakeSequence += 1;
|
|
264
|
+
persistSyncState(true);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
207
267
|
if (_topic !== topic)
|
|
208
268
|
return;
|
|
209
269
|
const push = parseProjectViewPush(payload);
|
|
@@ -214,13 +274,33 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
214
274
|
opts.onInfo?.(`project-view cache cursor=${result.cursor} ${push.companyUid}/${push.projectId}`);
|
|
215
275
|
}
|
|
216
276
|
});
|
|
277
|
+
next.on("error", (_error) => {
|
|
278
|
+
if (stopped || gen !== generation)
|
|
279
|
+
return;
|
|
280
|
+
inboxSubscribed = false;
|
|
281
|
+
syncSubscribed = false;
|
|
282
|
+
actorUid = undefined;
|
|
283
|
+
clearInboxHeartbeat();
|
|
284
|
+
persistInboxState(false);
|
|
285
|
+
persistSyncState(false);
|
|
286
|
+
opts.onInfo?.("work cache mqtt error");
|
|
287
|
+
schedule();
|
|
288
|
+
try {
|
|
289
|
+
next.end(true);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
217
295
|
next.on("close", () => {
|
|
218
296
|
if (stopped || gen !== generation)
|
|
219
297
|
return;
|
|
220
298
|
inboxSubscribed = false;
|
|
221
|
-
|
|
299
|
+
syncSubscribed = false;
|
|
300
|
+
actorUid = undefined;
|
|
222
301
|
clearInboxHeartbeat();
|
|
223
302
|
persistInboxState(false);
|
|
303
|
+
persistSyncState(false);
|
|
224
304
|
schedule();
|
|
225
305
|
});
|
|
226
306
|
};
|
|
@@ -250,7 +330,10 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
250
330
|
timers.clearTimeout(timer);
|
|
251
331
|
clearInboxHeartbeat();
|
|
252
332
|
inboxSubscribed = false;
|
|
333
|
+
syncSubscribed = false;
|
|
334
|
+
actorUid = undefined;
|
|
253
335
|
persistInboxState(false);
|
|
336
|
+
persistSyncState(false);
|
|
254
337
|
const current = client;
|
|
255
338
|
client = null;
|
|
256
339
|
if (!current)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/** Content-free sequence file written by the mesh MQTT inbox subscriber. */
|
|
6
6
|
export declare const AGENT_INBOX_PUSH_STATE_BASENAME = "agent-inbox-push.json";
|
|
7
|
+
export declare const SYNC_WAKE_STATE_BASENAME = "sync-wake.json";
|
|
7
8
|
export declare function workMeshRoot(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
8
9
|
export declare function workMeshSpoolPath(root?: string): string;
|
|
9
10
|
export declare function workMeshHeldPath(root?: string): string;
|
|
@@ -7,6 +7,7 @@ import * as path from "node:path";
|
|
|
7
7
|
const WORK_MESH_DIRNAME = "work-mesh";
|
|
8
8
|
/** Content-free sequence file written by the mesh MQTT inbox subscriber. */
|
|
9
9
|
export const AGENT_INBOX_PUSH_STATE_BASENAME = "agent-inbox-push.json";
|
|
10
|
+
export const SYNC_WAKE_STATE_BASENAME = "sync-wake.json";
|
|
10
11
|
export function workMeshRoot(home = os.homedir(), env = process.env) {
|
|
11
12
|
const override = env.HQ_WORK_MESH_ROOT?.trim();
|
|
12
13
|
if (override)
|
|
@@ -38,6 +38,8 @@ function allowed(al, c) {
|
|
|
38
38
|
}
|
|
39
39
|
if (al.commits.length)
|
|
40
40
|
checks.push(c.commit !== undefined && al.commits.includes(c.commit.toLowerCase()));
|
|
41
|
+
if (al.exact?.length)
|
|
42
|
+
checks.push(al.exact.includes(c.secret));
|
|
41
43
|
if (!checks.length)
|
|
42
44
|
return false;
|
|
43
45
|
return al.condition === "AND" ? checks.every(Boolean) : checks.some(Boolean);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fixed corpus of realistic credential lines, used to judge a repo
|
|
3
|
+
* allowlist by what it does: each line is placed in a file at the allowlist's
|
|
4
|
+
* paths and run through the real detector. Values are generated from a fixed
|
|
5
|
+
* seed at runtime, so no secret-shaped literal sits in this file and the
|
|
6
|
+
* corpus is the same on every run.
|
|
7
|
+
*/
|
|
8
|
+
export interface Probe {
|
|
9
|
+
kind: string;
|
|
10
|
+
line: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function credentialProbes(): Probe[];
|
|
13
|
+
//# sourceMappingURL=probes.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fixed corpus of realistic credential lines, used to judge a repo
|
|
3
|
+
* allowlist by what it does: each line is placed in a file at the allowlist's
|
|
4
|
+
* paths and run through the real detector. Values are generated from a fixed
|
|
5
|
+
* seed at runtime, so no secret-shaped literal sits in this file and the
|
|
6
|
+
* corpus is the same on every run.
|
|
7
|
+
*/
|
|
8
|
+
const UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
9
|
+
const LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
10
|
+
const DIGITS = "0123456789";
|
|
11
|
+
const ALNUM = UPPER + LOWER + DIGITS;
|
|
12
|
+
const HEX = "0123456789abcdef";
|
|
13
|
+
const B64URL = ALNUM + "-_";
|
|
14
|
+
/** Deterministic xorshift32 generator. */
|
|
15
|
+
function generator(seed) {
|
|
16
|
+
let s = seed >>> 0 || 1;
|
|
17
|
+
return (alphabet, n) => {
|
|
18
|
+
let out = "";
|
|
19
|
+
for (let i = 0; i < n; i++) {
|
|
20
|
+
s ^= s << 13;
|
|
21
|
+
s >>>= 0;
|
|
22
|
+
s ^= s >>> 17;
|
|
23
|
+
s ^= s << 5;
|
|
24
|
+
s >>>= 0;
|
|
25
|
+
out += alphabet[s % alphabet.length];
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const b64url = (s) => Buffer.from(s).toString("base64url");
|
|
31
|
+
const j = (...parts) => parts.join("");
|
|
32
|
+
let cached;
|
|
33
|
+
export function credentialProbes() {
|
|
34
|
+
if (cached)
|
|
35
|
+
return cached;
|
|
36
|
+
const r = generator(0x9e3779b9);
|
|
37
|
+
const probes = [];
|
|
38
|
+
const add = (kind, line) => probes.push({ kind, line });
|
|
39
|
+
// GitHub tokens: classic PAT, OAuth, app and fine-grained PAT.
|
|
40
|
+
add("GitHub PAT", `token = "${j("gh", "p_", r(ALNUM, 36))}"`);
|
|
41
|
+
add("GitHub PAT", `export GH_TOKEN=${j("gh", "o_", r(ALNUM, 36))}`);
|
|
42
|
+
add("GitHub PAT", ` "github": "${j("gh", "s_", r(ALNUM, 36))}",`);
|
|
43
|
+
add("GitHub PAT", j("gith", "ub_pat_", r(ALNUM, 22), "_", r(ALNUM, 59)));
|
|
44
|
+
// AWS access key ids.
|
|
45
|
+
add("AWS key", `aws_access_key_id = ${j("AK", "IA", r(UPPER + "234567", 16))}`);
|
|
46
|
+
add("AWS key", `AWS_ACCESS_KEY_ID="${j("AS", "IA", r(UPPER + "234567", 16))}"`);
|
|
47
|
+
add("AWS key", j("AK", "IA", r(UPPER + "234567", 16)));
|
|
48
|
+
// Slack bot and user tokens.
|
|
49
|
+
add("Slack token", `SLACK_BOT_TOKEN=${j("xo", "xb-", r(DIGITS, 12), "-", r(DIGITS, 13), "-", r(ALNUM, 24))}`);
|
|
50
|
+
add("Slack token", `slack: "${j("xo", "xb-", r(DIGITS, 11), "-", r(DIGITS, 12), "-", r(ALNUM, 24))}"`);
|
|
51
|
+
add("Slack token", `token = "${j("xo", "xp-", r(DIGITS, 12), "-", r(DIGITS, 12), "-", r(DIGITS, 13), "-", r(HEX, 32))}"`);
|
|
52
|
+
// 64-character hex secrets under credential-looking keys.
|
|
53
|
+
add("hex-64", `api_secret = "${r(HEX, 64)}"`);
|
|
54
|
+
add("hex-64", ` "auth_token": "${r(HEX, 64)}",`);
|
|
55
|
+
add("hex-64", `SECRET_KEY=${r(HEX, 64)}`);
|
|
56
|
+
// JWTs of different lengths.
|
|
57
|
+
for (const n of [24, 48, 96]) {
|
|
58
|
+
const header = b64url('{"alg":"HS256","typ":"JWT"}');
|
|
59
|
+
const payload = b64url(JSON.stringify({ sub: r(DIGITS, 10), name: r(LOWER, n / 4), iat: 1700000000 }));
|
|
60
|
+
add("JWT", `const jwt = "${header}.${payload}.${r(B64URL, 43)}";`);
|
|
61
|
+
}
|
|
62
|
+
// Generic key = value credentials of different lengths.
|
|
63
|
+
add("generic api_key", `api_key = "${r(ALNUM, 20)}"`);
|
|
64
|
+
add("generic api_key", `client_secret: '${r(ALNUM, 32)}'`);
|
|
65
|
+
add("generic api_key", `ACCESS_TOKEN=${r(ALNUM, 48)}`);
|
|
66
|
+
cached = probes;
|
|
67
|
+
return probes;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=probes.js.map
|
|
@@ -18,13 +18,28 @@
|
|
|
18
18
|
* literal prefix (`^golden/...`); `regexes` and `stopwords` need
|
|
19
19
|
* `condition = "AND"`, so they never reach files outside those paths.
|
|
20
20
|
*
|
|
21
|
+
* File scans (`hq scan secrets <files> --repo`, used by pr-check) honor only
|
|
22
|
+
* exact-value exemptions: each allowlist names its paths and lists, in
|
|
23
|
+
* `stopwords`, the exact secret values it allows (e.g. known digests). A
|
|
24
|
+
* detected secret is allowed only when it equals one of them. Regexes,
|
|
25
|
+
* targetRules, commits, regexTarget and path-only allowlists are rejected
|
|
26
|
+
* there, because a pattern can always be widened, alone or combined with other
|
|
27
|
+
* entries, to hide a real credential.
|
|
28
|
+
*
|
|
21
29
|
* Fails closed: anything not acceptable is a ScanConfigError, and the scan
|
|
22
30
|
* exits 2 rather than running with a guess.
|
|
23
31
|
*/
|
|
32
|
+
import { type Probe } from "./probes.js";
|
|
24
33
|
import { type CompiledAllowlist } from "./rules.js";
|
|
25
34
|
export declare class ScanConfigError extends Error {
|
|
26
35
|
}
|
|
27
36
|
export declare const REPO_CONFIG_FILE = ".gitleaks.toml";
|
|
37
|
+
/**
|
|
38
|
+
* The first probe credential the allowlist would hide, judged as the detector
|
|
39
|
+
* judges it (every regex ORed, every check combined by its condition, its
|
|
40
|
+
* targetRules applied), for a file at one of its paths. undefined: none.
|
|
41
|
+
*/
|
|
42
|
+
export declare function suppressedProbe(al: CompiledAllowlist): Probe | undefined;
|
|
28
43
|
/**
|
|
29
44
|
* Length of the literal text right after a leading `^`: letters, digits,
|
|
30
45
|
* `/ _ - @ ~` and backslash-escaped punctuation. A quantifier on the last
|
|
@@ -33,9 +48,18 @@ export declare const REPO_CONFIG_FILE = ".gitleaks.toml";
|
|
|
33
48
|
export declare function literalPathPrefix(src: string): number;
|
|
34
49
|
/** Validated allowlists from the text of a .gitleaks.toml. */
|
|
35
50
|
export declare function parseRepoAllowlists(text: string, file?: string): CompiledAllowlist[];
|
|
51
|
+
/** Validated exact-value allowlists for a file scan, from the text of a .gitleaks.toml. */
|
|
52
|
+
export declare function parseExactAllowlists(text: string, file?: string): CompiledAllowlist[];
|
|
36
53
|
/**
|
|
37
54
|
* Compiled allowlists for a staged scan of the repo at `repoRoot`, read from
|
|
38
55
|
* git as described at the top of this file. [] when the repo has none.
|
|
39
56
|
*/
|
|
40
57
|
export declare function loadRepoAllowlists(repoRoot: string): CompiledAllowlist[];
|
|
58
|
+
/**
|
|
59
|
+
* Exact-value allowlists from the copy committed at `rev` (default HEAD), for a
|
|
60
|
+
* file scan (`hq scan secrets --repo [--allowlist-rev]`). The index and working
|
|
61
|
+
* tree are ignored. pr-check passes the base branch, so a branch cannot exempt
|
|
62
|
+
* its own commits by adding an allowlist. An unknown rev is an error.
|
|
63
|
+
*/
|
|
64
|
+
export declare function loadCommittedAllowlists(repoRoot: string, rev?: string): CompiledAllowlist[];
|
|
41
65
|
//# sourceMappingURL=repo-config.d.ts.map
|