Files
oss-sync 57702b4f34
CI / build-and-test (push) Canceled after 0s
sync: update from private repo (2336949a)
2026-09-17 11:29:31 +00:00

81 lines
3.0 KiB
JavaScript

const DIGEST_PIN = /^[^\s@]+:[^\s@]+@sha256:[a-f0-9]{64}$/;
const EXACT_PYTHON_PIN = /^[A-Za-z0-9_.-]+(?:\[[A-Za-z0-9_,.-]+\])?==[^*<>=!~\s;]+(?:\s*;.+)?$/;
export function checkContainerPolicy(dockerfile) {
const violations = [];
for (const [index, line] of dockerfile.split(/\r?\n/).entries()) {
const match = line.match(/^\s*FROM\s+(\S+)/i);
if (!match || match[1].toLowerCase() === 'scratch') continue;
if (!DIGEST_PIN.test(match[1])) {
violations.push({ code: 'unpinned-image', line: index + 1, value: match[1] });
}
}
return violations;
}
/**
* Every `npm ci` / `npm install` in the image must carry `--ignore-scripts`.
*
* Why this check exists
* ---------------------
* The builder stage had `--ignore-scripts` from the start; the runtime stage —
* the one that actually ships — did not, and it installs `build-essential` on
* the same layer. A hijacked dependency's install script would have run there
* with a compiler in reach: the most permissive spot in the pipeline. The
* design and the PR both claimed install scripts were "off everywhere", so the
* gap was invisible in the prose (found reviewing PR #1074).
*
* Only RUN instructions are inspected, and comment lines are dropped first:
* this Dockerfile explains the Playwright bin collision in prose that mentions
* `npm ci` several times, and matching those would make the check cry wolf.
* Line continuations are joined, because the runtime install is one link in a
* long `&&` chain spread over many lines.
*/
export function checkDockerfileInstallPolicy(dockerfile) {
const violations = [];
const lines = dockerfile.split(/\r?\n/);
let buffer = '';
let startLine = 0;
let inRun = false;
for (const [index, raw] of lines.entries()) {
const line = raw.replace(/\s+#.*$/, '');
if (/^\s*#/.test(raw)) continue;
if (!inRun) {
if (!/^\s*RUN\s/i.test(line)) continue;
inRun = true;
startLine = index + 1;
buffer = '';
}
buffer += line.replace(/\\\s*$/, ' ');
if (/\\\s*$/.test(line)) continue;
for (const command of buffer.split(/&&|;|\|\|/)) {
if (!/\bnpm\b[^|]*\s(?:ci|install|i)(?:\s|$)/.test(command)) continue;
if (/--ignore-scripts\b/.test(command)) continue;
violations.push({
code: 'npm-install-runs-scripts',
line: startLine,
value: command.trim(),
});
}
buffer = '';
inRun = false;
}
return violations;
}
export function checkPythonRequirements(requirements) {
const violations = [];
for (const [index, rawLine] of requirements.split(/\r?\n/).entries()) {
const value = rawLine.replace(/\s+#.*$/, '').trim();
if (!value || value.startsWith('#')) continue;
if (value.startsWith('-') || value.includes(' @ ') || /(?:https?|git\+|file):/i.test(value)) {
violations.push({ code: 'non-index-python-dependency', line: index + 1, value });
continue;
}
if (!EXACT_PYTHON_PIN.test(value)) {
violations.push({ code: 'unpinned-python-dependency', line: index + 1, value });
}
}
return violations;
}