@arjunkhera/atlas 0.4.17 → 0.4.18
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.
|
@@ -292,7 +292,9 @@ export function freshTree(top, tree, sha) {
|
|
|
292
292
|
// commit has a fixed author, date and message, so it holds no history and names neither real commit.
|
|
293
293
|
export function gitTree(tree) {
|
|
294
294
|
const env = { PATH: process.env.PATH ?? '/usr/bin:/bin', HOME: tree, GIT_CONFIG_NOSYSTEM: '1', GIT_AUTHOR_NAME: 'atlas', GIT_AUTHOR_EMAIL: 'atlas@invalid', GIT_COMMITTER_NAME: 'atlas', GIT_COMMITTER_EMAIL: 'atlas@invalid', GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z', GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z' };
|
|
295
|
-
|
|
295
|
+
// No auto gc or background maintenance: it can still write in .git while
|
|
296
|
+
// the run removes the tree (ENOTEMPTY on atlas#223).
|
|
297
|
+
for (const args of [['init', '-q', '-b', 'main'], ['config', 'gc.auto', '0'], ['config', 'maintenance.auto', 'false'], ['add', '-A'], ['commit', '-q', '--no-verify', '--no-gpg-sign', '-m', 'tree']]) {
|
|
296
298
|
const r = spawnSync('git', args, { cwd: tree, env, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
297
299
|
if (r.status !== 0) throw new Error(`git ${args[0]} in the run tree failed: ${String(r.stderr).trim().split('\n')[0]}`);
|
|
298
300
|
}
|
|
@@ -322,7 +324,7 @@ export function runSide({ top, changed, plans, side, work = join(tmpdir(), 'atla
|
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
326
|
gitTree(tree);
|
|
325
|
-
for (const dir of ['home', 'tmp', 'report']) { rmSync(join(work, dir), { recursive: true, force: true }); mkdirSync(join(work, dir), { recursive: true }); }
|
|
327
|
+
for (const dir of ['home', 'tmp', 'report']) { rmSync(join(work, dir), { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); mkdirSync(join(work, dir), { recursive: true }); }
|
|
326
328
|
const command = commandOf(p.runner, p.path, report);
|
|
327
329
|
if (python && p.runner === 'pytest' && spawnSync('python3', ['-B', '-c', 'import pytest'], { cwd: tree, env }).status !== 0) {
|
|
328
330
|
out.files[p.path] = { command, cases: null, why: 'pytest is not installed', none: true };
|
package/door/lib/gate-check.mjs
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import { spawnSync } from 'node:child_process';
|
|
27
27
|
import { readFileSync, appendFileSync } from 'node:fs';
|
|
28
28
|
import { resolve } from 'node:path';
|
|
29
|
-
import { classify, changedPaths, compile, matcher } from './owner-only.mjs';
|
|
29
|
+
import { classify, changedPaths, compile, matcher, fakeSecret } from './owner-only.mjs';
|
|
30
30
|
import { listChanged } from './changed-tests.mjs';
|
|
31
31
|
import { parseYaml, SECRET_PATTERNS } from '../../shape/check.mjs';
|
|
32
32
|
import { brakeState, undoOf, tripText } from './brake.mjs';
|
|
@@ -101,7 +101,7 @@ export function secretRows(root, base, head, changes) {
|
|
|
101
101
|
for (const added of addedLines(root, base, head, change.path)) {
|
|
102
102
|
const found = patterns.find((pattern) => {
|
|
103
103
|
const hit = pattern.regex.exec(added.text);
|
|
104
|
-
return hit && !(pattern.id === 'url-credential' && EXAMPLE_HOST.test(hit[0]));
|
|
104
|
+
return hit && !fakeSecret(pattern.id, hit[0]) && !(pattern.id === 'url-credential' && EXAMPLE_HOST.test(hit[0]));
|
|
105
105
|
});
|
|
106
106
|
if (found) rows.push({ kind: 'red', class: 'secret', path: change.path, line: added.line, cause: `secret pattern ${found.id}` });
|
|
107
107
|
}
|
package/door/lib/gate.mjs
CHANGED
|
@@ -237,14 +237,12 @@ export function refusal(run, { name, pr, sha, baseRef, homes, sides = () => [] }
|
|
|
237
237
|
// the side branch's file, so it does not count.
|
|
238
238
|
if (pulls.some((each) => each.base?.ref !== baseRef)) return `also of a pull request into ${pulls.find((each) => each.base?.ref !== baseRef).base?.ref ?? 'another branch'}`;
|
|
239
239
|
// GitHub builds that list when it is read, from the open pull requests of
|
|
240
|
-
// the head branch. Once a side pull request closes, its
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (side.length) return `the head branch also had #${side[0].number} into ${side[0].base?.ref ?? 'another branch'}`;
|
|
247
|
-
}
|
|
240
|
+
// the head branch. Once a side pull request closes, its run names only this
|
|
241
|
+
// one. So no run counts when the head branch ever had a pull request into
|
|
242
|
+
// another branch (atlas#212 for the gate; item 043f8c00 for every check).
|
|
243
|
+
const side = sides();
|
|
244
|
+
if (side === null) return 'no head branch to check for side pull requests';
|
|
245
|
+
if (side.length) return `the head branch also had #${side[0].number} into ${side[0].base?.ref ?? 'another branch'}`;
|
|
248
246
|
return null;
|
|
249
247
|
}
|
|
250
248
|
|
|
@@ -268,7 +266,7 @@ export function jobRefusal(job, check, run) {
|
|
|
268
266
|
function pinnedRuns(gh, repo, { pr, sha, baseRef, head, checks }) {
|
|
269
267
|
const homes = homesAt(gh, repo, baseRef, checks);
|
|
270
268
|
// Every pull request, open or closed, from the head branch into another
|
|
271
|
-
// branch. Read once,
|
|
269
|
+
// branch. Read once, when the first run needs it.
|
|
272
270
|
let sideList;
|
|
273
271
|
const sides = () => {
|
|
274
272
|
if (sideList !== undefined) return sideList;
|
package/door/lib/owner-only.mjs
CHANGED
|
@@ -498,6 +498,38 @@ const newest = (paths) => paths.reduce((best, path) => (best === null || newer(p
|
|
|
498
498
|
|
|
499
499
|
// `rules` names the commit whose list holds; the default is the base. The
|
|
500
500
|
// gate replay passes today's main branch, to ask what today's list would do.
|
|
501
|
+
// Item b6e264a9, from the 30-day replay: the loose pattern "named-value"
|
|
502
|
+
// (token = ..., secret: ...) matched test code in most of its hits, such as
|
|
503
|
+
// a fake environment value. It does not count in a test file or under
|
|
504
|
+
// archive/. The patterns of a known shape still count there.
|
|
505
|
+
export const LOOSE_PATTERN = 'named-value';
|
|
506
|
+
// A spec/ folder is not on the list: a schema repo keeps product text there.
|
|
507
|
+
export const TEST_PATH = /(^|\/)(test|tests|__tests__)\/|\.(test|spec)\.[^/]+$|(^|\/)test_[^/]*\.py$|_test\.(py|go)$|^archive\//;
|
|
508
|
+
// A value that says it is fake, such as "sk-fake-model-key-...", is no key.
|
|
509
|
+
export const FAKE_VALUE = /fake|dummy|placeholder|example/i;
|
|
510
|
+
// The part of a hit that holds the value: the user and password of a URL
|
|
511
|
+
// credential, the text after ":" or "=" of a named value, else the whole
|
|
512
|
+
// token. The word test reads only that part, so a host such as
|
|
513
|
+
// db.examplecorp.io or a word after the value does not hide a real secret
|
|
514
|
+
// (verifier, atlas#221).
|
|
515
|
+
export function secretValue(id, text) {
|
|
516
|
+
if (id === 'url-credential') return text.slice(text.indexOf('://') + 3, text.lastIndexOf('@'));
|
|
517
|
+
if (id === LOOSE_PATTERN) return (/[:=]\s*(.*)$/s.exec(text)?.[1] ?? text).replace(/["',;})\]].*$/s, '');
|
|
518
|
+
return text;
|
|
519
|
+
}
|
|
520
|
+
export const fakeSecret = (id, text) => FAKE_VALUE.test(secretValue(id, text));
|
|
521
|
+
// The first secret pattern that one added line of this path shows, or null.
|
|
522
|
+
export function secretIn(patterns, path, lines) {
|
|
523
|
+
for (const pattern of patterns) {
|
|
524
|
+
if (pattern.id === LOOSE_PATTERN && TEST_PATH.test(path)) continue;
|
|
525
|
+
for (const line of lines) {
|
|
526
|
+
const hit = pattern.regex.exec(line);
|
|
527
|
+
if (hit && !fakeSecret(pattern.id, hit[0])) return { pattern, hit };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
|
|
501
533
|
export function classify({ root, base, head, rules: rulesRef = null, classes = loadClasses() }) {
|
|
502
534
|
const { rules, product, tree } = rulesAt({ root, ref: rulesRef ?? base, classes });
|
|
503
535
|
const baseTree = rulesRef ? treeAt(root, base) : tree;
|
|
@@ -544,7 +576,7 @@ export function classify({ root, base, head, rules: rulesRef = null, classes = l
|
|
|
544
576
|
if (kitRelease && sign.version_key) continue;
|
|
545
577
|
if (sign.type === 'path' || sign.type === 'any_change') hits.push([sign.class, cause]);
|
|
546
578
|
else if (sign.type === 'secret') {
|
|
547
|
-
const found = secrets
|
|
579
|
+
const found = secretIn(secrets, path, changesOf().added)?.pattern;
|
|
548
580
|
if (found) hits.push([sign.class, `${cause} (${found.id})`]);
|
|
549
581
|
} else if (sign.type === 'added_line') {
|
|
550
582
|
const regex = compile(sign.pattern);
|
package/package.json
CHANGED