feat: add multi-engine regex flavour support
This commit is contained in:
132
scripts/release-path-hygiene.mjs
Normal file
132
scripts/release-path-hygiene.mjs
Normal file
@@ -0,0 +1,132 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const DOCUMENTED_UPSTREAM_PATH_ARTIFACTS = Object.freeze({
|
||||
"engines/perl/emperl.data":
|
||||
"9529019418cf766a42cf2d25bd3fc97b47c9e689f5666cfc32dc11338d1b1e66",
|
||||
"engines/ruby/ruby.wasm":
|
||||
"fa1d5af1b4d601191739e6c4980d060d0d202181a7c83abf4930b3ca35b90f94",
|
||||
});
|
||||
|
||||
const LOCAL_PATH_PREFIXES = Object.freeze([
|
||||
"/mnt/",
|
||||
"/Users/",
|
||||
"/home/",
|
||||
...Array.from(
|
||||
{ length: 26 },
|
||||
(_, index) => `${String.fromCharCode(65 + index)}:\\Users\\`,
|
||||
),
|
||||
...Array.from(
|
||||
{ length: 26 },
|
||||
(_, index) => `${String.fromCharCode(65 + index)}:/Users/`,
|
||||
),
|
||||
...Array.from(
|
||||
{ length: 26 },
|
||||
(_, index) => `${String.fromCharCode(97 + index)}:\\Users\\`,
|
||||
),
|
||||
...Array.from(
|
||||
{ length: 26 },
|
||||
(_, index) => `${String.fromCharCode(97 + index)}:/Users/`,
|
||||
),
|
||||
]);
|
||||
|
||||
function sha256(value) {
|
||||
return createHash("sha256").update(value).digest("hex");
|
||||
}
|
||||
|
||||
function utf16BigEndian(value) {
|
||||
const encoded = Buffer.from(value, "utf16le");
|
||||
for (let index = 0; index < encoded.byteLength; index += 2) {
|
||||
const first = encoded[index];
|
||||
encoded[index] = encoded[index + 1];
|
||||
encoded[index + 1] = first;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function encodedForms(value) {
|
||||
return [
|
||||
{ encoding: "ASCII/UTF-8", value: Buffer.from(value, "utf8") },
|
||||
{ encoding: "UTF-16LE", value: Buffer.from(value, "utf16le") },
|
||||
{ encoding: "UTF-16BE", value: utf16BigEndian(value) },
|
||||
];
|
||||
}
|
||||
|
||||
function startsWithAt(data, value, offset) {
|
||||
return (
|
||||
offset + value.byteLength <= data.byteLength &&
|
||||
data.subarray(offset, offset + value.byteLength).equals(value)
|
||||
);
|
||||
}
|
||||
|
||||
function isAllowedVirtualHome(data, offset, encoding, allowedVirtualHomes) {
|
||||
return allowedVirtualHomes.some((home) => {
|
||||
const encodedHome = encodedForms(home).find(
|
||||
(candidate) => candidate.encoding === encoding,
|
||||
)?.value;
|
||||
if (!encodedHome || !startsWithAt(data, encodedHome, offset)) return false;
|
||||
const end = offset + encodedHome.byteLength;
|
||||
if (end === data.byteLength) return true;
|
||||
const character =
|
||||
encoding === "UTF-16LE" && end + 1 < data.byteLength
|
||||
? data.readUInt16LE(end)
|
||||
: encoding === "UTF-16BE" && end + 1 < data.byteLength
|
||||
? data.readUInt16BE(end)
|
||||
: data[end];
|
||||
return (
|
||||
character === 0 ||
|
||||
character === 0x2f ||
|
||||
!(
|
||||
(character >= 0x30 && character <= 0x39) ||
|
||||
(character >= 0x41 && character <= 0x5a) ||
|
||||
(character >= 0x61 && character <= 0x7a) ||
|
||||
character === 0x2e ||
|
||||
character === 0x5f ||
|
||||
character === 0x2d
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function localAbsolutePathMatch(
|
||||
input,
|
||||
artifactName,
|
||||
{ allowedVirtualHomes = [] } = {},
|
||||
) {
|
||||
const data = Buffer.from(input);
|
||||
const documentedDigest = DOCUMENTED_UPSTREAM_PATH_ARTIFACTS[artifactName];
|
||||
if (documentedDigest && sha256(data) === documentedDigest) return null;
|
||||
|
||||
for (const prefix of LOCAL_PATH_PREFIXES) {
|
||||
for (const encoded of encodedForms(prefix)) {
|
||||
let offset = data.indexOf(encoded.value);
|
||||
while (offset !== -1) {
|
||||
if (
|
||||
prefix !== "/home/" ||
|
||||
!isAllowedVirtualHome(
|
||||
data,
|
||||
offset,
|
||||
encoded.encoding,
|
||||
allowedVirtualHomes,
|
||||
)
|
||||
) {
|
||||
return { prefix, encoding: encoded.encoding };
|
||||
}
|
||||
offset = data.indexOf(encoded.value, offset + encoded.value.byteLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function assertNoUnexpectedLocalPaths(
|
||||
input,
|
||||
artifactName,
|
||||
options = {},
|
||||
) {
|
||||
const match = localAbsolutePathMatch(input, artifactName, options);
|
||||
if (match) {
|
||||
throw new Error(
|
||||
`Local absolute path detected in ${artifactName} (${match.encoding} ${match.prefix}).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user