feat: add multi-engine regex flavour support

This commit is contained in:
2026-07-27 11:43:51 +02:00
parent 7079cde15f
commit 7f3a91ad37
340 changed files with 643286 additions and 483 deletions

View 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}).`,
);
}
}