cabloy 5.1.228 → 5.1.229
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/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.229
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -366,81 +366,45 @@ test('stream fatal UTF-8 and line limit never emit raw fragments of the failing
|
|
|
366
366
|
await assert.rejects(streamed(['/Users/Secret/raw']), /^Error: INVALID_INPUT$/u);
|
|
367
367
|
});
|
|
368
368
|
|
|
369
|
-
test('stream
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
await assert.rejects(
|
|
381
|
-
sanitizeStream(Readable.from([bytes]), output.stream, { matcher }),
|
|
382
|
-
error => {
|
|
383
|
-
assert.equal(String(error), 'Error: BINARY');
|
|
384
|
-
assert.equal(error.code, 'BINARY');
|
|
385
|
-
assert.deepEqual(error.details, { byte, line: 1, bytePosition });
|
|
386
|
-
return true;
|
|
387
|
-
},
|
|
388
|
-
);
|
|
389
|
-
assert.equal(output.text(), '');
|
|
390
|
-
const result = cli(['sanitize'], bytes);
|
|
391
|
-
assert.equal(result.status, 2);
|
|
392
|
-
assert.equal(result.stdout, '');
|
|
393
|
-
assert.equal(
|
|
394
|
-
result.stderr,
|
|
395
|
-
`evidence-paths: BINARY byte=${byte} line=1 byte-position=${bytePosition}\n`,
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
369
|
+
test('stream preserves controls on complete and final lines while sanitizing paths', async () => {
|
|
370
|
+
const controls = '\u0000\u0001\u001B\u007F';
|
|
371
|
+
const input = `${controls}/Users/Secret/file${controls} /Users/Secret${controls} /synthetic/checkout/a${controls} file:///Users/Secret/file${controls}`;
|
|
372
|
+
const expected = `${controls}<PRIVATE_PATH>${controls} <PRIVATE_PATH>${controls} a${controls} <PRIVATE_PATH>${controls}`;
|
|
373
|
+
for (const ending of ['', '\n']) {
|
|
374
|
+
const bytes = Buffer.from(input + ending);
|
|
375
|
+
assert.equal(await streamed([bytes]), expected + ending);
|
|
376
|
+
const result = cli(['sanitize', '--repo-root', '/synthetic/checkout'], bytes);
|
|
377
|
+
assert.equal(result.status, 0);
|
|
378
|
+
assert.equal(result.stdout, expected + ending);
|
|
379
|
+
assert.equal(result.stderr, '');
|
|
399
380
|
}
|
|
400
381
|
assert.equal(await streamed([Buffer.from('plain\ttext\f\r\n')]), 'plain\ttext\f\r\n');
|
|
401
382
|
});
|
|
402
383
|
|
|
403
|
-
test('
|
|
384
|
+
test('stream preserves ANSI sequences and controls across UTF-8 chunks and lines', async () => {
|
|
385
|
+
const input = 'ok\r\n\r\né🙂\u001B[31m /Users/Secret/file\u001B[0m\u0000';
|
|
386
|
+
const expected = 'ok\r\n\r\né🙂\u001B[31m <PRIVATE_PATH>\u001B[0m\u0000';
|
|
404
387
|
for (const ending of ['', '\n']) {
|
|
405
|
-
const bytes = Buffer.
|
|
406
|
-
Buffer.from('ok\r\n\r\né🙂'),
|
|
407
|
-
Buffer.from([27, 0]),
|
|
408
|
-
Buffer.from('/Users/Secret/file'),
|
|
409
|
-
Buffer.from(ending),
|
|
410
|
-
]);
|
|
388
|
+
const bytes = Buffer.from(input + ending);
|
|
411
389
|
for (const chunks of [[bytes], Array.from(bytes, byte => Buffer.from([byte]))]) {
|
|
390
|
+
assert.equal(await streamed(chunks), expected + ending);
|
|
412
391
|
const output = sink();
|
|
413
|
-
await assert.rejects(
|
|
414
|
-
sanitizeStream(Readable.from(chunks), output.stream, { matcher }),
|
|
415
|
-
error => {
|
|
416
|
-
assert.equal(String(error), 'Error: BINARY');
|
|
417
|
-
assert.equal(error.code, 'BINARY');
|
|
418
|
-
assert.deepEqual(error.details, { byte: 27, line: 3, bytePosition: 7 });
|
|
419
|
-
return true;
|
|
420
|
-
},
|
|
421
|
-
);
|
|
422
|
-
assert.equal(output.text(), 'ok\r\n\r\n');
|
|
423
|
-
const cliOutput = sink();
|
|
424
392
|
const errors = sink();
|
|
425
393
|
assert.equal(
|
|
426
394
|
await runCli(['sanitize'], {
|
|
427
395
|
input: Readable.from(chunks),
|
|
428
|
-
output:
|
|
396
|
+
output: output.stream,
|
|
429
397
|
error: errors.stream,
|
|
430
398
|
}),
|
|
431
|
-
|
|
399
|
+
0,
|
|
432
400
|
);
|
|
433
|
-
assert.equal(
|
|
434
|
-
assert.equal(errors.text(), '
|
|
401
|
+
assert.equal(output.text(), expected + ending);
|
|
402
|
+
assert.equal(errors.text(), '');
|
|
435
403
|
}
|
|
436
|
-
const result = cli(['sanitize'], bytes);
|
|
437
|
-
assert.equal(result.status, 2);
|
|
438
|
-
assert.equal(result.stdout, 'ok\r\n\r\n');
|
|
439
|
-
assert.equal(result.stderr, 'evidence-paths: BINARY byte=27 line=3 byte-position=7\n');
|
|
440
404
|
}
|
|
441
405
|
});
|
|
442
406
|
|
|
443
|
-
test('line limits
|
|
407
|
+
test('line byte limits still apply to control-containing input', async () => {
|
|
444
408
|
for (const ending of ['', '\n']) {
|
|
445
409
|
const bytes = Buffer.concat([
|
|
446
410
|
Buffer.from([27]),
|
|
@@ -9,15 +9,14 @@ export const LINE_LIMIT = 1024 * 1024;
|
|
|
9
9
|
export const FILE_LIMIT = 8 * 1024 * 1024;
|
|
10
10
|
|
|
11
11
|
class SafeError extends Error {
|
|
12
|
-
constructor(code
|
|
12
|
+
constructor(code) {
|
|
13
13
|
super(code);
|
|
14
14
|
this.code = code;
|
|
15
|
-
this.details = details;
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
function fail(code
|
|
20
|
-
throw new SafeError(code
|
|
18
|
+
function fail(code) {
|
|
19
|
+
throw new SafeError(code);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
function rootInfo(value) {
|
|
@@ -70,7 +69,7 @@ function pathEnd(text, start, quoted) {
|
|
|
70
69
|
let end = start;
|
|
71
70
|
while (end < text.length) {
|
|
72
71
|
const char = text[end];
|
|
73
|
-
if (/[\
|
|
72
|
+
if (/[\p{Cc}"'`<>(){}[\]|,;!?]/u.test(char) || (!quoted && /\s/u.test(char))) break;
|
|
74
73
|
// Preserve stack line/column suffixes (and keep other colon-delimited prose out).
|
|
75
74
|
if (char === ':') break;
|
|
76
75
|
end++;
|
|
@@ -120,14 +119,15 @@ export function createMatcher({ repoRoot = DEFAULT_REPO_ROOT, maps = [] } = {})
|
|
|
120
119
|
);
|
|
121
120
|
|
|
122
121
|
const sep = '(?:\\\\{1,2}|/)';
|
|
123
|
-
const account =
|
|
122
|
+
const account =
|
|
123
|
+
'[^/\\\\\\s\\p{Cc}"\'`<>(){}[\\]|,;:!?]+(?: [^/\\\\\\s\\p{Cc}"\'`<>(){}[\\]|,;:!?]+)*';
|
|
124
124
|
const known = [
|
|
125
|
-
new RegExp(`/(?:Users|home)/${account}`, '
|
|
125
|
+
new RegExp(`/(?:Users|home)/${account}`, 'uy'),
|
|
126
126
|
/\/(?:private\/)?tmp/y,
|
|
127
127
|
/\/(?:private\/)?var\/folders/y,
|
|
128
|
-
new RegExp(`[A-Z]:${sep}(?:Users${sep}${account}|(?:Windows${sep})?Temp)`, '
|
|
128
|
+
new RegExp(`[A-Z]:${sep}(?:Users${sep}${account}|(?:Windows${sep})?Temp)`, 'iuy'),
|
|
129
129
|
// UNC is a filesystem convention; forward-slash protocol-relative URLs are not.
|
|
130
|
-
/\\{2,4}[^\\/\s"'`<>(){}[\]|,;:!?]+\\{1,2}[^\\/\s"'`<>(){}[\]|,;:!?]+/
|
|
130
|
+
/\\{2,4}[^\\/\s\p{Cc}"'`<>(){}[\]|,;:!?]+\\{1,2}[^\\/\s\p{Cc}"'`<>(){}[\]|,;:!?]+/uy,
|
|
131
131
|
];
|
|
132
132
|
|
|
133
133
|
function recognizeAt(text, start, quoted = false) {
|
|
@@ -136,7 +136,7 @@ export function createMatcher({ repoRoot = DEFAULT_REPO_ROOT, maps = [] } = {})
|
|
|
136
136
|
const match = entry.regex.exec(text);
|
|
137
137
|
if (!match) continue;
|
|
138
138
|
const after = start + match[0].length;
|
|
139
|
-
if (after < text.length && !/[/\\\s"'`<>(){}[\]|,;:!?]/u.test(text[after])) continue;
|
|
139
|
+
if (after < text.length && !/[/\\\s\p{Cc}"'`<>(){}[\]|,;:!?]/u.test(text[after])) continue;
|
|
140
140
|
const end = pathEnd(text, after, quoted);
|
|
141
141
|
const tail = normalizeTail(text.slice(after, end), entry.windows).replace(/^\/+|\/+$/gu, '');
|
|
142
142
|
const replacement =
|
|
@@ -154,7 +154,7 @@ export function createMatcher({ repoRoot = DEFAULT_REPO_ROOT, maps = [] } = {})
|
|
|
154
154
|
const match = regex.exec(text);
|
|
155
155
|
if (!match) continue;
|
|
156
156
|
const after = start + match[0].length;
|
|
157
|
-
if (after < text.length && !/[/\\\s"'`<>(){}[\]|,;:!?]/u.test(text[after])) continue;
|
|
157
|
+
if (after < text.length && !/[/\\\s\p{Cc}"'`<>(){}[\]|,;:!?]/u.test(text[after])) continue;
|
|
158
158
|
return { end: pathEnd(text, after, quoted), replacement: '<PRIVATE_PATH>' };
|
|
159
159
|
}
|
|
160
160
|
return null;
|
|
@@ -164,7 +164,7 @@ export function createMatcher({ repoRoot = DEFAULT_REPO_ROOT, maps = [] } = {})
|
|
|
164
164
|
if (typeof text !== 'string') fail('INVALID_TEXT');
|
|
165
165
|
const matches = [];
|
|
166
166
|
// Skip non-file URLs as units, including their paths, query strings and fragments.
|
|
167
|
-
const urls = /[A-Za-z][A-Za-z0-9+.-]{0,31}:\/\/[^\s"'`<>(){}[\]|]+/gu;
|
|
167
|
+
const urls = /[A-Za-z][A-Za-z0-9+.-]{0,31}:\/\/[^\s\p{Cc}"'`<>(){}[\]|]+/gu;
|
|
168
168
|
const protectedUrls = [...text.matchAll(urls)];
|
|
169
169
|
let urlIndex = 0;
|
|
170
170
|
for (let index = 0; index < text.length; index++) {
|
|
@@ -248,7 +248,6 @@ export async function sanitizeStream(input, output, { matcher = createMatcher()
|
|
|
248
248
|
output.on('error', onError);
|
|
249
249
|
let parts = [];
|
|
250
250
|
let length = 0;
|
|
251
|
-
let lineNumber = 1;
|
|
252
251
|
try {
|
|
253
252
|
for await (const chunk of input) {
|
|
254
253
|
if (outputFailed) fail('OUTPUT_ERROR');
|
|
@@ -264,10 +263,8 @@ export async function sanitizeStream(input, output, { matcher = createMatcher()
|
|
|
264
263
|
parts.push(bytes.subarray(start, end));
|
|
265
264
|
if (newline < 0) break;
|
|
266
265
|
const line = Buffer.concat(parts, length);
|
|
267
|
-
rejectBinary(line, lineNumber);
|
|
268
266
|
const text = decode(line);
|
|
269
267
|
await write(output, `${matcher.sanitize(text)}\n`);
|
|
270
|
-
lineNumber++;
|
|
271
268
|
parts = [];
|
|
272
269
|
length = 0;
|
|
273
270
|
start = newline + 1;
|
|
@@ -275,7 +272,6 @@ export async function sanitizeStream(input, output, { matcher = createMatcher()
|
|
|
275
272
|
}
|
|
276
273
|
if (length) {
|
|
277
274
|
const line = Buffer.concat(parts, length);
|
|
278
|
-
rejectBinary(line, lineNumber);
|
|
279
275
|
await write(output, matcher.sanitize(decode(line)));
|
|
280
276
|
}
|
|
281
277
|
if (outputFailed) fail('OUTPUT_ERROR');
|
|
@@ -296,21 +292,12 @@ function safeFilename(value, matcher) {
|
|
|
296
292
|
);
|
|
297
293
|
}
|
|
298
294
|
|
|
299
|
-
function
|
|
300
|
-
return bytes.
|
|
295
|
+
function binary(bytes) {
|
|
296
|
+
return bytes.some(
|
|
301
297
|
byte => byte === 0 || byte === 127 || (byte < 32 && ![9, 10, 12, 13].includes(byte)),
|
|
302
298
|
);
|
|
303
299
|
}
|
|
304
300
|
|
|
305
|
-
function binary(bytes) {
|
|
306
|
-
return binaryIndex(bytes) >= 0;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function rejectBinary(bytes, line) {
|
|
310
|
-
const index = binaryIndex(bytes);
|
|
311
|
-
if (index >= 0) fail('BINARY', { byte: bytes[index], line, bytePosition: index + 1 });
|
|
312
|
-
}
|
|
313
|
-
|
|
314
301
|
async function ancestorExclusion(path) {
|
|
315
302
|
const root = parse(path).root;
|
|
316
303
|
let current = root;
|
|
@@ -476,13 +463,11 @@ Stack :line:column suffixes are preserved. Non-file URLs, unrelated API/containe
|
|
|
476
463
|
paths and unknown absolute roots are left unchanged: this is NOT a universal secret
|
|
477
464
|
scanner. Use explicit maps for other local roots; inspect output before publication.
|
|
478
465
|
UTF-8 decoding is fatal, with a 1 MiB byte limit per line (including CR, excluding LF).
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
line and byte-position within that original UTF-8 line. LF advances the line; CR counts
|
|
482
|
-
as a byte, not a line boundary. No text from the rejected line is reported.
|
|
466
|
+
sanitize preserves control characters without interpreting or stripping them; they do
|
|
467
|
+
not cause BINARY rejection. LF separates lines; CR counts toward the line byte limit.
|
|
483
468
|
Only complete validated lines are emitted; earlier sanitized lines may survive failure.
|
|
484
|
-
check limits each file to 8 MiB
|
|
485
|
-
scope-N/relative-filename:line CODE, never source lines or absolute scan roots.
|
|
469
|
+
check limits each file to 8 MiB and treats NUL, DEL and non-text C0 controls as binary.
|
|
470
|
+
It reports only scope-N/relative-filename:line CODE, never source lines or absolute scan roots.
|
|
486
471
|
Symlinks (including scope ancestors), special files, .env* files/directories, unreadable,
|
|
487
472
|
binary, invalid UTF-8 and over-limit files make checking incomplete. Scope trees must
|
|
488
473
|
remain stable while scanning; this is not an adversarial filesystem snapshot.
|
|
@@ -545,23 +530,8 @@ export async function runCli(
|
|
|
545
530
|
return result.status;
|
|
546
531
|
} catch (caught) {
|
|
547
532
|
const code = caught instanceof SafeError ? caught.code : 'INTERNAL_ERROR';
|
|
548
|
-
let diagnostic = code;
|
|
549
|
-
if (caught instanceof SafeError && code === 'BINARY') {
|
|
550
|
-
const { byte, line, bytePosition } = caught.details ?? {};
|
|
551
|
-
if (
|
|
552
|
-
Number.isInteger(byte) &&
|
|
553
|
-
byte >= 0 &&
|
|
554
|
-
byte <= 255 &&
|
|
555
|
-
Number.isSafeInteger(line) &&
|
|
556
|
-
line > 0 &&
|
|
557
|
-
Number.isSafeInteger(bytePosition) &&
|
|
558
|
-
bytePosition > 0
|
|
559
|
-
) {
|
|
560
|
-
diagnostic += ` byte=${byte} line=${line} byte-position=${bytePosition}`;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
533
|
try {
|
|
564
|
-
await write(error, `evidence-paths: ${
|
|
534
|
+
await write(error, `evidence-paths: ${code}\n`);
|
|
565
535
|
} catch {
|
|
566
536
|
/* no unsafe fallback */
|
|
567
537
|
}
|
package/repo-docs/pnpm-lock.yaml
CHANGED
|
@@ -697,8 +697,8 @@ packages:
|
|
|
697
697
|
shiki@2.5.0:
|
|
698
698
|
resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==}
|
|
699
699
|
|
|
700
|
-
source-map-js@1.2.
|
|
701
|
-
resolution: {integrity: sha512-
|
|
700
|
+
source-map-js@1.2.2:
|
|
701
|
+
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
|
|
702
702
|
engines: {node: '>=0.10.0'}
|
|
703
703
|
|
|
704
704
|
space-separated-tokens@2.0.2:
|
|
@@ -1175,7 +1175,7 @@ snapshots:
|
|
|
1175
1175
|
'@vue/shared': 3.5.35
|
|
1176
1176
|
entities: 7.0.1
|
|
1177
1177
|
estree-walker: 2.0.2
|
|
1178
|
-
source-map-js: 1.2.
|
|
1178
|
+
source-map-js: 1.2.2
|
|
1179
1179
|
|
|
1180
1180
|
'@vue/compiler-dom@3.5.35':
|
|
1181
1181
|
dependencies:
|
|
@@ -1192,7 +1192,7 @@ snapshots:
|
|
|
1192
1192
|
estree-walker: 2.0.2
|
|
1193
1193
|
magic-string: 0.30.21
|
|
1194
1194
|
postcss: 8.5.15
|
|
1195
|
-
source-map-js: 1.2.
|
|
1195
|
+
source-map-js: 1.2.2
|
|
1196
1196
|
|
|
1197
1197
|
'@vue/compiler-ssr@3.5.35':
|
|
1198
1198
|
dependencies:
|
|
@@ -1425,7 +1425,7 @@ snapshots:
|
|
|
1425
1425
|
dependencies:
|
|
1426
1426
|
nanoid: 3.3.12
|
|
1427
1427
|
picocolors: 1.1.1
|
|
1428
|
-
source-map-js: 1.2.
|
|
1428
|
+
source-map-js: 1.2.2
|
|
1429
1429
|
|
|
1430
1430
|
preact@10.29.2: {}
|
|
1431
1431
|
|
|
@@ -1487,7 +1487,7 @@ snapshots:
|
|
|
1487
1487
|
'@shikijs/vscode-textmate': 10.0.2
|
|
1488
1488
|
'@types/hast': 3.0.4
|
|
1489
1489
|
|
|
1490
|
-
source-map-js@1.2.
|
|
1490
|
+
source-map-js@1.2.2: {}
|
|
1491
1491
|
|
|
1492
1492
|
space-separated-tokens@2.0.2: {}
|
|
1493
1493
|
|