@codyswann/lisa 4.70.2 → 4.70.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +188 -0
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +271 -0
- package/all/copy-overwrite/scripts/lib/history-secret-scanner.mjs +6 -296
- package/all/copy-overwrite/scripts/lisa-history-secrets.mjs +8 -1
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +34 -8
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +26 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +14 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/prune-retired-entire-hooks.d.ts +25 -0
- package/dist/migrations/prune-retired-entire-hooks.d.ts.map +1 -0
- package/dist/migrations/prune-retired-entire-hooks.js +146 -0
- package/dist/migrations/prune-retired-entire-hooks.js.map +1 -0
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -64
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/.claude-plugin/plugin.json +0 -63
- package/rails/merge/.claude/settings.json +0 -47
- package/scripts/install-claude-plugins.sh +1 -1
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
gitRead,
|
|
28
28
|
HistorySecretError,
|
|
29
29
|
} from "./history-secret-git.mjs";
|
|
30
|
+
import { classifyEvidence } from "./history-secret-evidence.mjs";
|
|
30
31
|
|
|
31
32
|
export const VERSION = "8.30.1";
|
|
32
33
|
/** Archive and extracted executable pins both come from the same official release. */
|
|
@@ -222,303 +223,12 @@ export const qualifyScanner = requested => {
|
|
|
222
223
|
return binary;
|
|
223
224
|
};
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const NARRATIVE = [
|
|
229
|
-
"AccessDenied boundaries,",
|
|
230
|
-
`${TAXONOMY} falsifications.`,
|
|
231
|
-
].join(" ");
|
|
232
|
-
const safePath = value =>
|
|
233
|
-
typeof value === "string" &&
|
|
234
|
-
value.length > 0 &&
|
|
235
|
-
!/[\\\u0000-\u001f\u007f]/u.test(value) &&
|
|
236
|
-
!/^(?:\/|[A-Za-z]:)/u.test(value) &&
|
|
237
|
-
value.split("/").every(part => part && part !== "." && part !== "..");
|
|
238
|
-
|
|
239
|
-
/** Read authentic regular Git blobs, never trimmed text or worktree evidence. */
|
|
240
|
-
const readBlob = (file, commit, cwd, budget) => {
|
|
241
|
-
if (!safePath(file)) return null;
|
|
242
|
-
const entries = gitRead(
|
|
243
|
-
["--literal-pathspecs", "ls-tree", "-z", commit, "--", file],
|
|
244
|
-
cwd
|
|
245
|
-
)
|
|
246
|
-
.split("\0")
|
|
247
|
-
.filter(Boolean);
|
|
248
|
-
if (entries.length !== 1) return null;
|
|
249
|
-
const entry = entries[0].match(
|
|
250
|
-
/^(100644|100755) blob ([a-f0-9]{40}|[a-f0-9]{64})\t([\s\S]*)$/u
|
|
251
|
-
);
|
|
252
|
-
if (!entry || entry[3] !== file || entry[2].length !== commit.length)
|
|
253
|
-
return null;
|
|
254
|
-
const sizeText = gitRead(["cat-file", "-s", entry[2]], cwd);
|
|
255
|
-
const size = Number(sizeText);
|
|
256
|
-
if (!/^\d+$/u.test(sizeText) || !Number.isSafeInteger(size))
|
|
257
|
-
throw new HistorySecretError(
|
|
258
|
-
"Git blob size is invalid. Repair complete Git objects and retry; safety is unproved."
|
|
259
|
-
);
|
|
260
|
-
if (size > budget.remaining) return null;
|
|
261
|
-
const result = spawnSync(
|
|
262
|
-
"git",
|
|
263
|
-
[NO_REPLACEMENTS, "cat-file", "blob", entry[2]],
|
|
264
|
-
{
|
|
265
|
-
cwd,
|
|
266
|
-
env: gitEnvironment(),
|
|
267
|
-
timeout: 120000,
|
|
268
|
-
maxBuffer: EVIDENCE_BUDGET,
|
|
269
|
-
}
|
|
270
|
-
);
|
|
271
|
-
if (
|
|
272
|
-
result.error ||
|
|
273
|
-
result.signal ||
|
|
274
|
-
result.status !== 0 ||
|
|
275
|
-
result.stdout.length !== size ||
|
|
276
|
-
createHash(commit.length === 40 ? "sha1" : "sha256")
|
|
277
|
-
.update(`blob ${size}\0`)
|
|
278
|
-
.update(result.stdout)
|
|
279
|
-
.digest("hex") !== entry[2]
|
|
280
|
-
)
|
|
226
|
+
/** Only structured safe attribution is serialized, never stderr or metadata strings. */
|
|
227
|
+
export const scanCommits = (commits, cwd, requested, heads = []) => {
|
|
228
|
+
if (!Array.isArray(heads) || heads.some(head => !commits.includes(head)))
|
|
281
229
|
throw new HistorySecretError(
|
|
282
|
-
"
|
|
230
|
+
"Evidence heads are not actual introduced commits. Supply original Git input and retry."
|
|
283
231
|
);
|
|
284
|
-
budget.remaining -= size;
|
|
285
|
-
return result.stdout;
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
/** Native JSON validation plus bounded duplicate-aware lexical role attribution. */
|
|
289
|
-
const evidenceMap = bytes => {
|
|
290
|
-
if (bytes.length > 1024 * 1024) return null;
|
|
291
|
-
const text = bytes.toString("utf8");
|
|
292
|
-
if (!Buffer.from(text).equals(bytes)) return null;
|
|
293
|
-
try {
|
|
294
|
-
const value = JSON.parse(text);
|
|
295
|
-
if (
|
|
296
|
-
!value ||
|
|
297
|
-
typeof value !== "object" ||
|
|
298
|
-
Array.isArray(value) ||
|
|
299
|
-
!value.source_sha256 ||
|
|
300
|
-
typeof value.source_sha256 !== "object" ||
|
|
301
|
-
Array.isArray(value.source_sha256)
|
|
302
|
-
)
|
|
303
|
-
return null;
|
|
304
|
-
const entries = Object.entries(value.source_sha256);
|
|
305
|
-
if (
|
|
306
|
-
!entries.length ||
|
|
307
|
-
entries.length > 256 ||
|
|
308
|
-
entries.some(
|
|
309
|
-
([file, hash]) =>
|
|
310
|
-
!safePath(file) ||
|
|
311
|
-
typeof hash !== "string" ||
|
|
312
|
-
!/^[a-f0-9]{64}$/u.test(hash)
|
|
313
|
-
)
|
|
314
|
-
)
|
|
315
|
-
return null;
|
|
316
|
-
const tokens = [
|
|
317
|
-
...text.matchAll(/"(?:\\[\s\S]|[^"\\])*"|[{}[\],:]|[^{}[\],:\s]+/gu),
|
|
318
|
-
];
|
|
319
|
-
if (tokens.length > 8192) return null;
|
|
320
|
-
let cursor = 0;
|
|
321
|
-
const spans = [];
|
|
322
|
-
const visit = (path, depth) => {
|
|
323
|
-
if (depth > 32) throw new Error("Bounded JSON depth");
|
|
324
|
-
const token = tokens[cursor++];
|
|
325
|
-
if (token[0] === "{") {
|
|
326
|
-
const keys = new Set();
|
|
327
|
-
while (tokens[cursor][0] !== "}") {
|
|
328
|
-
const key = JSON.parse(tokens[cursor++][0]);
|
|
329
|
-
if (keys.has(key)) throw new Error("Duplicate decoded JSON key");
|
|
330
|
-
keys.add(key);
|
|
331
|
-
cursor++; // Native JSON validation already proved the colon.
|
|
332
|
-
visit([...path, key], depth + 1);
|
|
333
|
-
if (tokens[cursor][0] === ",") cursor++;
|
|
334
|
-
}
|
|
335
|
-
cursor++;
|
|
336
|
-
} else if (token[0] === "[") {
|
|
337
|
-
let index = 0;
|
|
338
|
-
while (tokens[cursor][0] !== "]") {
|
|
339
|
-
visit([...path, index++], depth + 1);
|
|
340
|
-
if (tokens[cursor][0] === ",") cursor++;
|
|
341
|
-
}
|
|
342
|
-
cursor++;
|
|
343
|
-
} else if (
|
|
344
|
-
path.length === 2 &&
|
|
345
|
-
path[0] === "source_sha256" &&
|
|
346
|
-
token[0][0] === '"'
|
|
347
|
-
) {
|
|
348
|
-
const hash = JSON.parse(token[0]);
|
|
349
|
-
// Escaped values do not supply an unambiguous literal detector span.
|
|
350
|
-
if (token[0] !== JSON.stringify(hash))
|
|
351
|
-
throw new Error("Ambiguous digest spelling");
|
|
352
|
-
spans.push({
|
|
353
|
-
file: path[1],
|
|
354
|
-
hash,
|
|
355
|
-
start: Buffer.byteLength(text.slice(0, token.index + 1)),
|
|
356
|
-
end: Buffer.byteLength(
|
|
357
|
-
text.slice(0, token.index + token[0].length - 1)
|
|
358
|
-
),
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
visit([], 0);
|
|
363
|
-
return cursor === tokens.length && spans.length === entries.length
|
|
364
|
-
? { value, spans }
|
|
365
|
-
: null;
|
|
366
|
-
} catch {
|
|
367
|
-
// probe-direction: fail-closed — malformed, duplicate or over-budget JSON
|
|
368
|
-
// cannot establish evidence role; its detector findings remain blocking.
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
/** Clear only a uniquely attributed, independently established benign finding. */
|
|
374
|
-
const classifyEvidence = cwd => {
|
|
375
|
-
const budget = { remaining: EVIDENCE_BUDGET };
|
|
376
|
-
const blobs = new Map();
|
|
377
|
-
const maps = new Map();
|
|
378
|
-
const blob = (file, commit) => {
|
|
379
|
-
const key = `${commit}\0${file}`;
|
|
380
|
-
if (!blobs.has(key)) {
|
|
381
|
-
if (blobs.size >= 256) return null;
|
|
382
|
-
blobs.set(key, readBlob(file, commit, cwd, budget));
|
|
383
|
-
}
|
|
384
|
-
return blobs.get(key);
|
|
385
|
-
};
|
|
386
|
-
return row => {
|
|
387
|
-
if (
|
|
388
|
-
row.RuleID !== "generic-api-key" ||
|
|
389
|
-
!safePath(row.File) ||
|
|
390
|
-
row.SymlinkFile !== "" ||
|
|
391
|
-
row.Secret !== "REDACTED" ||
|
|
392
|
-
typeof row.Match !== "string" ||
|
|
393
|
-
row.EndLine !== row.StartLine ||
|
|
394
|
-
!Number.isSafeInteger(row.StartColumn) ||
|
|
395
|
-
!Number.isSafeInteger(row.EndColumn) ||
|
|
396
|
-
row.StartColumn < 1 ||
|
|
397
|
-
row.EndColumn < row.StartColumn
|
|
398
|
-
)
|
|
399
|
-
return false;
|
|
400
|
-
const bytes = blob(row.File, row.Commit);
|
|
401
|
-
if (!bytes) return false;
|
|
402
|
-
let lineStart = 0;
|
|
403
|
-
for (let line = 1; line < row.StartLine; line++) {
|
|
404
|
-
const next = bytes.indexOf(10, lineStart);
|
|
405
|
-
if (next < 0) return false;
|
|
406
|
-
lineStart = next + 1;
|
|
407
|
-
}
|
|
408
|
-
const newline = bytes.indexOf(10, lineStart);
|
|
409
|
-
const lineEnd = newline < 0 ? bytes.length : newline;
|
|
410
|
-
const replacements = row.Match.split("REDACTED");
|
|
411
|
-
if (replacements.length !== 2) return false;
|
|
412
|
-
const line = bytes.subarray(lineStart, lineEnd);
|
|
413
|
-
const attributed = (candidate, left, right) => {
|
|
414
|
-
const template = replacements.join(candidate);
|
|
415
|
-
if (template.split(candidate).length !== 2) return false;
|
|
416
|
-
const original = Buffer.from(template);
|
|
417
|
-
const offset = line.indexOf(original);
|
|
418
|
-
if (offset < 0 || line.indexOf(original, offset + 1) >= 0) return false;
|
|
419
|
-
const start = lineStart + offset;
|
|
420
|
-
const end = start + original.length;
|
|
421
|
-
if (
|
|
422
|
-
left !== start + Buffer.byteLength(replacements[0]) ||
|
|
423
|
-
right !== left + Buffer.byteLength(candidate)
|
|
424
|
-
)
|
|
425
|
-
return false;
|
|
426
|
-
// Pinned 8.30.1 detect/location.go counts a preceding fragment newline
|
|
427
|
-
// in columns after its first line. Prove exactly one of those two byte
|
|
428
|
-
// frames against the actual unique Git span; never infer fragment state.
|
|
429
|
-
return (
|
|
430
|
-
[0, 1].filter(
|
|
431
|
-
newlineByte =>
|
|
432
|
-
start === lineStart + row.StartColumn - 1 - newlineByte &&
|
|
433
|
-
end === lineStart + row.EndColumn - newlineByte
|
|
434
|
-
).length === 1
|
|
435
|
-
);
|
|
436
|
-
};
|
|
437
|
-
// A complete standalone validation paragraph excludes surrounding JSON,
|
|
438
|
-
// assignments and interpolated/code contexts, not merely their filenames.
|
|
439
|
-
if (
|
|
440
|
-
(bytes.equals(Buffer.from(NARRATIVE)) ||
|
|
441
|
-
bytes.equals(Buffer.from(`${NARRATIVE}\n`))) &&
|
|
442
|
-
attributed(
|
|
443
|
-
TAXONOMY,
|
|
444
|
-
NARRATIVE.indexOf(TAXONOMY),
|
|
445
|
-
NARRATIVE.indexOf(TAXONOMY) + TAXONOMY.length
|
|
446
|
-
)
|
|
447
|
-
)
|
|
448
|
-
return true;
|
|
449
|
-
const key = `${row.Commit}\0${row.File}`;
|
|
450
|
-
if (!maps.has(key)) {
|
|
451
|
-
const parsed = evidenceMap(bytes);
|
|
452
|
-
let valid = parsed !== null;
|
|
453
|
-
const revision =
|
|
454
|
-
parsed && Object.hasOwn(parsed.value, "source_revision")
|
|
455
|
-
? parsed.value.source_revision
|
|
456
|
-
: row.Commit;
|
|
457
|
-
if (parsed && Object.hasOwn(parsed.value, "source_revision")) {
|
|
458
|
-
valid =
|
|
459
|
-
typeof revision === "string" &&
|
|
460
|
-
new RegExp(`^[a-f0-9]{${row.Commit.length}}$`, "u").test(revision);
|
|
461
|
-
if (valid) {
|
|
462
|
-
const type = spawnSync(
|
|
463
|
-
"git",
|
|
464
|
-
[NO_REPLACEMENTS, "cat-file", "-t", revision],
|
|
465
|
-
{
|
|
466
|
-
cwd,
|
|
467
|
-
env: gitEnvironment(),
|
|
468
|
-
timeout: 120000,
|
|
469
|
-
maxBuffer: 1024 * 1024,
|
|
470
|
-
}
|
|
471
|
-
);
|
|
472
|
-
const ancestry = spawnSync(
|
|
473
|
-
"git",
|
|
474
|
-
[
|
|
475
|
-
NO_REPLACEMENTS,
|
|
476
|
-
"merge-base",
|
|
477
|
-
"--is-ancestor",
|
|
478
|
-
revision,
|
|
479
|
-
row.Commit,
|
|
480
|
-
],
|
|
481
|
-
{
|
|
482
|
-
cwd,
|
|
483
|
-
env: gitEnvironment(),
|
|
484
|
-
timeout: 120000,
|
|
485
|
-
maxBuffer: 1024 * 1024,
|
|
486
|
-
}
|
|
487
|
-
);
|
|
488
|
-
// probe-direction: fail-closed — an absent/unreachable revision or
|
|
489
|
-
// failed ancestry probe cannot establish a historical preimage.
|
|
490
|
-
valid =
|
|
491
|
-
!type.error &&
|
|
492
|
-
!type.signal &&
|
|
493
|
-
type.status === 0 &&
|
|
494
|
-
type.stdout.toString() === "commit\n" &&
|
|
495
|
-
!type.stderr.length &&
|
|
496
|
-
!ancestry.error &&
|
|
497
|
-
!ancestry.signal &&
|
|
498
|
-
ancestry.status === 0 &&
|
|
499
|
-
!ancestry.stdout.length &&
|
|
500
|
-
!ancestry.stderr.length;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
if (valid)
|
|
504
|
-
for (const span of parsed.spans) {
|
|
505
|
-
const sourceBytes = blob(span.file, revision);
|
|
506
|
-
if (!sourceBytes || digest(sourceBytes) !== span.hash) {
|
|
507
|
-
valid = false;
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
maps.set(key, valid ? parsed.spans : []);
|
|
512
|
-
}
|
|
513
|
-
const candidates = maps
|
|
514
|
-
.get(key)
|
|
515
|
-
.filter(span => attributed(span.hash, span.start, span.end));
|
|
516
|
-
return candidates.length === 1;
|
|
517
|
-
};
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
/** Only structured safe attribution is serialized, never stderr or metadata strings. */
|
|
521
|
-
export const scanCommits = (commits, cwd, requested) => {
|
|
522
232
|
const binary = qualifyScanner(requested);
|
|
523
233
|
const scratch = mkdtempSync(join(tmpdir(), "lisa-history-scan-"));
|
|
524
234
|
chmodSync(scratch, 0o700);
|
|
@@ -558,7 +268,7 @@ export const scanCommits = (commits, cwd, requested) => {
|
|
|
558
268
|
});
|
|
559
269
|
const findings = [];
|
|
560
270
|
const audit = auditedGit(scratch);
|
|
561
|
-
const benign = classifyEvidence(cwd);
|
|
271
|
+
const benign = classifyEvidence(cwd, commits, [...new Set(heads)]);
|
|
562
272
|
for (let offset = 0; offset < commits.length; offset += 100) {
|
|
563
273
|
const group = commits.slice(offset, offset + 100);
|
|
564
274
|
audit.begin();
|
|
@@ -25,6 +25,7 @@ export const main = async (
|
|
|
25
25
|
const {
|
|
26
26
|
HistorySecretError,
|
|
27
27
|
objectWidth,
|
|
28
|
+
gitRead,
|
|
28
29
|
parsePush,
|
|
29
30
|
eventPairs,
|
|
30
31
|
introducedCommits,
|
|
@@ -91,7 +92,13 @@ export const main = async (
|
|
|
91
92
|
console.log("No introduced history. Scanner was not run.");
|
|
92
93
|
return 0;
|
|
93
94
|
}
|
|
94
|
-
const
|
|
95
|
+
const heads = pairs
|
|
96
|
+
.filter(pair => !/^0+$/u.test(pair.after))
|
|
97
|
+
.map(pair =>
|
|
98
|
+
gitRead(["rev-parse", "--verify", `${pair.after}^{commit}`], cwd)
|
|
99
|
+
)
|
|
100
|
+
.filter(head => commits.includes(head));
|
|
101
|
+
const result = scanCommits(commits, cwd, scanner, heads);
|
|
95
102
|
console.log(JSON.stringify(result));
|
|
96
103
|
if (result.findings.length > 0) {
|
|
97
104
|
console.error(
|
|
@@ -1077,17 +1077,36 @@ function trackerContract(config = readConfig()) {
|
|
|
1077
1077
|
);
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
|
+
/**
|
|
1081
|
+
* Resolve authored references before any binding or provider decision.
|
|
1082
|
+
*
|
|
1083
|
+
* The literal `github/` namespace is an identity spelling, not a different
|
|
1084
|
+
* tracker or an exemption. Its ASCII segments must survive unchanged; URL
|
|
1085
|
+
* parsing or path normalization would incorrectly admit encoded/dot paths.
|
|
1086
|
+
* @param {unknown} raw Authored work-item reference.
|
|
1087
|
+
* @param {object} contract Existing configured tracker contract.
|
|
1088
|
+
* @returns {string} Repository-scoped canonical identity.
|
|
1089
|
+
*/
|
|
1080
1090
|
function canonicalizeRef(raw, contract = trackerContract()) {
|
|
1081
1091
|
const value = String(raw ?? "").trim();
|
|
1082
1092
|
if (contract.provider === "github") {
|
|
1093
|
+
const qualified =
|
|
1094
|
+
/^github\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#([1-9]\d*)$/.exec(value);
|
|
1083
1095
|
// Match authored URL bytes before normalization: URL parsers can erase a
|
|
1084
1096
|
// port or decode a path that this input contract deliberately refuses.
|
|
1085
1097
|
const match =
|
|
1098
|
+
qualified ??
|
|
1086
1099
|
/^([^\s/#]+\/[^\s/#]+)#([1-9]\d*)$/.exec(value) ??
|
|
1087
1100
|
/^https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/issues\/([1-9]\d*)$/.exec(
|
|
1088
1101
|
value
|
|
1089
1102
|
);
|
|
1090
|
-
if (
|
|
1103
|
+
if (
|
|
1104
|
+
!match ||
|
|
1105
|
+
(qualified &&
|
|
1106
|
+
qualified[1]
|
|
1107
|
+
.split("/")
|
|
1108
|
+
.some(segment => segment === "." || segment === ".."))
|
|
1109
|
+
)
|
|
1091
1110
|
throw new TrackingError(
|
|
1092
1111
|
`Invalid GitHub Work-Item '${value}'; expected owner/repo#123`
|
|
1093
1112
|
);
|
|
@@ -4542,6 +4561,10 @@ function backlink(args) {
|
|
|
4542
4561
|
* repository does the commit declare? — and a value the filter drops is one the
|
|
4543
4562
|
* reader still saw. That ordering is what keeps a malformed trailer refusable
|
|
4544
4563
|
* by the gate instead of invisible to it.
|
|
4564
|
+
* Qualified `github/owner/name#number` values use the SAME canonical resolver
|
|
4565
|
+
* as the gates, so the audit does not lose attribution accepted by a hook.
|
|
4566
|
+
* This audit still requires a hash-number declaration; supported issue URLs
|
|
4567
|
+
* remain a gate input, not a newly admitted audit declaration.
|
|
4545
4568
|
*
|
|
4546
4569
|
* CRLF needs nothing here, and a relayed review finding that said otherwise
|
|
4547
4570
|
* was refuted rather than acted on. `workItemLines` splits on `/\r?\n/`, so a
|
|
@@ -4553,16 +4576,19 @@ function backlink(args) {
|
|
|
4553
4576
|
* @returns {number[]} Declared issue numbers, de-duplicated, in first-seen order.
|
|
4554
4577
|
*/
|
|
4555
4578
|
export function declaredWorkItemNumbers(body, repository) {
|
|
4556
|
-
const owner = String(repository ?? "")
|
|
4579
|
+
const owner = String(repository ?? "");
|
|
4557
4580
|
if (owner === "") return [];
|
|
4581
|
+
const contract = { provider: "github", repository: owner };
|
|
4558
4582
|
const numbers = [];
|
|
4559
4583
|
for (const value of workItemLines(body)) {
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4584
|
+
if (!value.includes("#")) continue;
|
|
4585
|
+
try {
|
|
4586
|
+
const ref = canonicalizeRef(value, contract);
|
|
4587
|
+
const digits = ref.slice(ref.indexOf("#") + 1);
|
|
4588
|
+
if (isIssueNumber(digits)) numbers.push(Number(digits));
|
|
4589
|
+
} catch (error) {
|
|
4590
|
+
if (!(error instanceof TrackingError)) throw error;
|
|
4591
|
+
}
|
|
4566
4592
|
}
|
|
4567
4593
|
return [...new Set(numbers)];
|
|
4568
4594
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAo8CjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAu2CjC,CAAC"}
|
|
@@ -447,6 +447,15 @@ export const LISA_OWNED_HASH_LEDGER = Object.freeze({
|
|
|
447
447
|
"d234617dc907bb563455419957f2f419702fcd1c10b9d39e19d5bab8ca962008",
|
|
448
448
|
"e07bab7dc4c4c296e2007cf26fab694b1238545dc37816e88d849606d8f9e760",
|
|
449
449
|
]),
|
|
450
|
+
"scripts/lib/history-secret-evidence-shape.mjs": Object.freeze([
|
|
451
|
+
"346393e1ba8a5fa9b35d14f86745e62de87b06b5626418e65ff6589d7a4828fc",
|
|
452
|
+
"f9b606645b9f3d55031e9cd0caf874395ae17ec6425f4a2adf6ca29e87560f0e",
|
|
453
|
+
"ff421c676daf8098f15d59f46a285076b0a9e4a94bdf5484f739cffc2fe5f8eb",
|
|
454
|
+
]),
|
|
455
|
+
"scripts/lib/history-secret-evidence.mjs": Object.freeze([
|
|
456
|
+
"0475ac4654e33c2f24e0f10adbe79eb9db3229e1d2eb5160418b78545043410c",
|
|
457
|
+
"81247f4a85322a96e5ef049acec12b84925cc8fbac90b4a6811479f8a311e2b2",
|
|
458
|
+
]),
|
|
450
459
|
"scripts/lib/history-secret-git.mjs": Object.freeze([
|
|
451
460
|
"102276aef106e07b2b385fe8f2d3b54a9d30381633a22e07d5d74a5db9ddf320",
|
|
452
461
|
"254907d808a76d4f338d1cb86e9262c7e2992386a79cd417bc65b385f8253715",
|
|
@@ -460,6 +469,7 @@ export const LISA_OWNED_HASH_LEDGER = Object.freeze({
|
|
|
460
469
|
"501452c19be923616a6ea1ed9443c86cbac14e4aa3073e579f6af82d1423a2c9",
|
|
461
470
|
"6afcb5ea42d8f9046a17e2253d4ddbefdf9025b150870afda23db77796f05f25",
|
|
462
471
|
"a68a4ba784699c470e1525ed3cd2f31f399cc80472d2bf5653d936bf3252c658",
|
|
472
|
+
"d77fc82db53ffbfef6bd714b600ed9f8b711cf52bbf630020efa4841ae5f083c",
|
|
463
473
|
"fc2677b9f5ae86ee9709bb59f1e9cf231f258d12ddcfbf9a6dd9a711155220d2",
|
|
464
474
|
"ff90535b7c6f24f48651ab93d204a1530c6bfaa8e6169d3aa83ab069c1a1ef22",
|
|
465
475
|
]),
|
|
@@ -794,6 +804,7 @@ export const LISA_OWNED_HASH_LEDGER = Object.freeze({
|
|
|
794
804
|
"2d6ae84741703a300d78379eb4bdf3935815aba715cb163a34378418d17eadf0",
|
|
795
805
|
"6887f865cee4cca397cac9f0efb366f2e5b9d4c68596f0306775709e354a85d2",
|
|
796
806
|
"7b55063eb78d7796aeb1d9c1de8b66882ccbfcb524e33773757ffa307d987177",
|
|
807
|
+
"966301ef764ddd5cfb37d26c22dd2dc4712496e67f63adddde9635fec6f6136c",
|
|
797
808
|
]),
|
|
798
809
|
"scripts/lisa-hooks/block-blind-automerge.sh": Object.freeze([
|
|
799
810
|
"3f58a1bc31a36ae1720591013cd59fd3e9046c865aae2a795b29a2405aefb6a1",
|
|
@@ -1260,6 +1271,7 @@ export const LISA_OWNED_HASH_LEDGER = Object.freeze({
|
|
|
1260
1271
|
"42176fa5ee6df11d2337a91b2eeeb31a9ed7cc1e16acb0cec11739ba5a6849ed",
|
|
1261
1272
|
"4372872a448d3b943dea5541167ed5be6155703b57b9f8224d19ddefd3b80898",
|
|
1262
1273
|
"475cb9ef86c2ac54c8fd8cb6d63e9a55ea8cd0017e26efbe3c44391a4aeeab41",
|
|
1274
|
+
"47677cf8b388e62fc82efdf564f94a784499f23b7f290ee1d7f9f50cc8a8abc3",
|
|
1263
1275
|
"4ae4e4e25c5fc075824a112a26abae6ab157c2888332c376db928b51530abc0a",
|
|
1264
1276
|
"4c263fef48c03b3b6c623ea8a6cd55505bfaf28197e5072226ea35f9f3d26885",
|
|
1265
1277
|
"4d054e3b7a266d69108f7cec12ee02e6a302670ff5932dc398cb0c5069b2a44f",
|
|
@@ -1287,6 +1299,7 @@ export const LISA_OWNED_HASH_LEDGER = Object.freeze({
|
|
|
1287
1299
|
"6afe796f5a69a68a026c79a64aa451eb2bf7907a0ace0d9079f8b4add16b886b",
|
|
1288
1300
|
"6ced6d3929f58e0b0b3e12717af537f9c4f57ad03c18d99e774026c169b13874",
|
|
1289
1301
|
"6dadc065016e741568b6ae1ebcd0378085005c076fdb93346bad7c1fd9e3b8b8",
|
|
1302
|
+
"6ec811517f212f6e6b33a8163f16a6b0538731c9cbe626e9f62a55b0f0af9518",
|
|
1290
1303
|
"7156449f0ad8c68c40c3f7f02df91248e32d9d83a439b38c4ce0a3fd35e04c1e",
|
|
1291
1304
|
"71fcbf510d3ac112417cea1bd0b4fd4b478e7f01a51f04b6a28fb7a487443d36",
|
|
1292
1305
|
"71ff42c2cb8ac7022e0066132476764dd745fb92bf19ccd395000b1fe319abc7",
|
|
@@ -1904,6 +1917,15 @@ export const LISA_OWNED_HASH_HISTORY_DERIVED = Object.freeze({
|
|
|
1904
1917
|
"d234617dc907bb563455419957f2f419702fcd1c10b9d39e19d5bab8ca962008",
|
|
1905
1918
|
"e07bab7dc4c4c296e2007cf26fab694b1238545dc37816e88d849606d8f9e760",
|
|
1906
1919
|
]),
|
|
1920
|
+
"scripts/lib/history-secret-evidence-shape.mjs": Object.freeze([
|
|
1921
|
+
"346393e1ba8a5fa9b35d14f86745e62de87b06b5626418e65ff6589d7a4828fc",
|
|
1922
|
+
"f9b606645b9f3d55031e9cd0caf874395ae17ec6425f4a2adf6ca29e87560f0e",
|
|
1923
|
+
"ff421c676daf8098f15d59f46a285076b0a9e4a94bdf5484f739cffc2fe5f8eb",
|
|
1924
|
+
]),
|
|
1925
|
+
"scripts/lib/history-secret-evidence.mjs": Object.freeze([
|
|
1926
|
+
"0475ac4654e33c2f24e0f10adbe79eb9db3229e1d2eb5160418b78545043410c",
|
|
1927
|
+
"81247f4a85322a96e5ef049acec12b84925cc8fbac90b4a6811479f8a311e2b2",
|
|
1928
|
+
]),
|
|
1907
1929
|
"scripts/lib/history-secret-git.mjs": Object.freeze([
|
|
1908
1930
|
"102276aef106e07b2b385fe8f2d3b54a9d30381633a22e07d5d74a5db9ddf320",
|
|
1909
1931
|
"254907d808a76d4f338d1cb86e9262c7e2992386a79cd417bc65b385f8253715",
|
|
@@ -1917,6 +1939,7 @@ export const LISA_OWNED_HASH_HISTORY_DERIVED = Object.freeze({
|
|
|
1917
1939
|
"501452c19be923616a6ea1ed9443c86cbac14e4aa3073e579f6af82d1423a2c9",
|
|
1918
1940
|
"6afcb5ea42d8f9046a17e2253d4ddbefdf9025b150870afda23db77796f05f25",
|
|
1919
1941
|
"a68a4ba784699c470e1525ed3cd2f31f399cc80472d2bf5653d936bf3252c658",
|
|
1942
|
+
"d77fc82db53ffbfef6bd714b600ed9f8b711cf52bbf630020efa4841ae5f083c",
|
|
1920
1943
|
"fc2677b9f5ae86ee9709bb59f1e9cf231f258d12ddcfbf9a6dd9a711155220d2",
|
|
1921
1944
|
"ff90535b7c6f24f48651ab93d204a1530c6bfaa8e6169d3aa83ab069c1a1ef22",
|
|
1922
1945
|
]),
|
|
@@ -2206,6 +2229,7 @@ export const LISA_OWNED_HASH_HISTORY_DERIVED = Object.freeze({
|
|
|
2206
2229
|
"2d6ae84741703a300d78379eb4bdf3935815aba715cb163a34378418d17eadf0",
|
|
2207
2230
|
"6887f865cee4cca397cac9f0efb366f2e5b9d4c68596f0306775709e354a85d2",
|
|
2208
2231
|
"7b55063eb78d7796aeb1d9c1de8b66882ccbfcb524e33773757ffa307d987177",
|
|
2232
|
+
"966301ef764ddd5cfb37d26c22dd2dc4712496e67f63adddde9635fec6f6136c",
|
|
2209
2233
|
]),
|
|
2210
2234
|
"scripts/lisa-hooks/block-blind-automerge.sh": Object.freeze([
|
|
2211
2235
|
"3f58a1bc31a36ae1720591013cd59fd3e9046c865aae2a795b29a2405aefb6a1",
|
|
@@ -2649,6 +2673,7 @@ export const LISA_OWNED_HASH_HISTORY_DERIVED = Object.freeze({
|
|
|
2649
2673
|
"42176fa5ee6df11d2337a91b2eeeb31a9ed7cc1e16acb0cec11739ba5a6849ed",
|
|
2650
2674
|
"4372872a448d3b943dea5541167ed5be6155703b57b9f8224d19ddefd3b80898",
|
|
2651
2675
|
"475cb9ef86c2ac54c8fd8cb6d63e9a55ea8cd0017e26efbe3c44391a4aeeab41",
|
|
2676
|
+
"47677cf8b388e62fc82efdf564f94a784499f23b7f290ee1d7f9f50cc8a8abc3",
|
|
2652
2677
|
"4ae4e4e25c5fc075824a112a26abae6ab157c2888332c376db928b51530abc0a",
|
|
2653
2678
|
"4c263fef48c03b3b6c623ea8a6cd55505bfaf28197e5072226ea35f9f3d26885",
|
|
2654
2679
|
"4d054e3b7a266d69108f7cec12ee02e6a302670ff5932dc398cb0c5069b2a44f",
|
|
@@ -2675,6 +2700,7 @@ export const LISA_OWNED_HASH_HISTORY_DERIVED = Object.freeze({
|
|
|
2675
2700
|
"6afe796f5a69a68a026c79a64aa451eb2bf7907a0ace0d9079f8b4add16b886b",
|
|
2676
2701
|
"6ced6d3929f58e0b0b3e12717af537f9c4f57ad03c18d99e774026c169b13874",
|
|
2677
2702
|
"6dadc065016e741568b6ae1ebcd0378085005c076fdb93346bad7c1fd9e3b8b8",
|
|
2703
|
+
"6ec811517f212f6e6b33a8163f16a6b0538731c9cbe626e9f62a55b0f0af9518",
|
|
2678
2704
|
"7156449f0ad8c68c40c3f7f02df91248e32d9d83a439b38c4ce0a3fd35e04c1e",
|
|
2679
2705
|
"71fcbf510d3ac112417cea1bd0b4fd4b478e7f01a51f04b6a28fb7a487443d36",
|
|
2680
2706
|
"71ff42c2cb8ac7022e0066132476764dd745fb92bf19ccd395000b1fe319abc7",
|