fix(preload): stop the sandbox guard failing on quoted and commented text

The three detection regexes ran over raw source, so a preload containing
`const help = "Call require('fs')"` failed the build with a node-builtin
violation, and a comment mentioning a module call did the same — nothing
minifies preload chunks, so comments survive into the emitted output.
A safe preload could not build.

Mask lexical spans first: one pass marks every comment and string
character while preserving offsets, and a match is vetoed only when its
module keyword lands on a masked offset. The regexes still run over the
original source, so real specifiers are unaffected, and template
interpolations stay unmasked — `${require('fs')}` is still a violation.
Rollup's resolved import lists remain the authoritative check.

Costs 104 production lines against a program rule that wants strictly
net-negative code. A dependency-free lexer cannot be written smaller, and
the alternative — trusting chunk metadata alone — would drop detection of
the renamed and non-literal `require` forms the guard exists to catch.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-08-07 22:49:02 -07:00
co-authored by Orca
parent 4742c11a0b
commit 03c3e6e722
2 changed files with 170 additions and 1 deletions
@@ -110,6 +110,71 @@ describe('preload sandbox guard detection', () => {
})
})
// Why: the case above only covers identifier prefixes, a quoted *static import statement* off
// statement position, and member-access `require`. None of them exercise call syntax inside a
// literal, so the guard used to fail the build on comments and ordinary strings.
describe('preload sandbox guard lexical masking', () => {
it('ignores a module call quoted inside a string literal', () => {
expect(findPreloadSandboxViolations(`const help = "Call require('fs')"`)).toEqual([])
})
it('ignores a module call in a line comment, which survives an unminified chunk', () => {
expect(findPreloadSandboxViolations("// use require('fs')\nexport {}")).toEqual([])
})
it('ignores a module call in a block comment', () => {
expect(findPreloadSandboxViolations("/* see require('fs')\n and import('path') */")).toEqual(
[]
)
})
it('ignores a module call inside a template literal', () => {
expect(findPreloadSandboxViolations('const t = `see import("path") here`')).toEqual([])
})
it('ignores a fake require after an escaped quote, which naive masking would end early', () => {
expect(findPreloadSandboxViolations('const s = "a\\"require(\'fs\')\\"b"')).toEqual([])
})
it('still flags a real request inside a template interpolation', () => {
expect(findPreloadSandboxViolations('const t = `${require("fs")}`')).toEqual([
{ kind: 'node-builtin', detail: 'fs' }
])
})
it('still flags a real request nested in an interpolated object literal', () => {
expect(findPreloadSandboxViolations('const t = `${({ a: require("net") }).a}` + "x"')).toEqual([
{ kind: 'node-builtin', detail: 'net' }
])
})
it('still flags a real request on the line after a comment', () => {
expect(findPreloadSandboxViolations('// hi\nconst x = require("node:os")')).toEqual([
{ kind: 'node-builtin', detail: 'node:os' }
])
})
// Why: same line on purpose — an unterminated string already stops at a newline, so only a
// same-line regex can swallow the request that follows it.
it('still flags a real request after a regex holding an unpaired quote', () => {
expect(findPreloadSandboxViolations('const q = /["]/; const x = require("node:zlib")')).toEqual(
[{ kind: 'node-builtin', detail: 'node:zlib' }]
)
})
it('still flags a real request after a division, which is not a regex', () => {
expect(findPreloadSandboxViolations('const r = t / 2; const x = require("node:dns")')).toEqual([
{ kind: 'node-builtin', detail: 'node:dns' }
])
})
it('still flags a real static import that follows a quoted decoy', () => {
expect(findPreloadSandboxViolations('const d = "require(\'fs\')";\nimport "node:tty"')).toEqual(
[{ kind: 'node-builtin', detail: 'node:tty' }]
)
})
})
describe('preload sandbox guard assertions', () => {
it('names every offending specifier and the supported surface', () => {
expect(() =>
@@ -63,19 +63,123 @@ function collect(
}
}
/** A NUL can never appear in real source, so a masked offset is unambiguous. */
const MASKED = '\u0000'
/**
* Marks every comment and string-literal character, preserving offsets, so a match can be tested
* for whether it sits in code or in data. Interpolations stay code: `${require('fs')}` is real.
*/
function maskLexicalSpans(code: string): string {
const chars = code.split('')
const mask = (from: number, to: number): void => {
for (let at = from; at < Math.min(to, chars.length); at += 1) {
chars[at] = MASKED
}
}
const interpolations: number[] = [] // brace depth inside each enclosing `${}`
let inTemplate = false
// A `/` here starts a regex, not a division; anything else makes it an operator.
let regexAllowed = true
let index = 0
while (index < code.length) {
const char = code[index]
const pair = code.slice(index, index + 2)
if (inTemplate) {
if (char === '`') {
inTemplate = false
regexAllowed = false
index += 1
} else if (pair === '${') {
interpolations.push(0)
inTemplate = false
index += 2
} else {
const width = char === '\\' ? 2 : 1
mask(index, index + width)
index += width
}
continue
}
if (pair === '//' || pair === '/*') {
const close = pair === '//' ? code.indexOf('\n', index) : code.indexOf('*/', index + 2)
const end = close === -1 ? code.length : pair === '//' ? close : close + 2
mask(index, end)
index = end
continue
}
if (char === "'" || char === '"') {
let scan = index + 1
// A raw newline ends an unterminated literal; nothing after it is still that string.
while (scan < code.length && code[scan] !== char && code[scan] !== '\n') {
scan += code[scan] === '\\' ? 2 : 1
}
mask(index, scan + 1)
index = scan + 1
regexAllowed = false
continue
}
// Why: an unpaired quote inside a regex would otherwise open a string over real code.
if (char === '/' && regexAllowed) {
let scan = index + 1
let inClass = false
while (scan < code.length && code[scan] !== '\n' && (inClass || code[scan] !== '/')) {
if (code[scan] === '\\') {
scan += 1
} else if (code[scan] === '[' || code[scan] === ']') {
inClass = code[scan] === '['
}
scan += 1
}
mask(index, scan + 1)
index = scan + 1
regexAllowed = false
continue
}
if (!/\s/.test(char)) {
regexAllowed = !/[\w$)\]]/.test(char)
}
if (char === '`') {
inTemplate = true
} else if (interpolations.length > 0 && (char === '{' || char === '}')) {
const last = interpolations.length - 1
if (char === '{') {
interpolations[last] += 1
} else if (interpolations[last] > 0) {
interpolations[last] -= 1
} else {
interpolations.pop()
inTemplate = true
}
}
index += 1
}
return chars.join('')
}
export function findPreloadSandboxViolations(code: string): PreloadSandboxViolation[] {
const violations = new Map<string, PreloadSandboxViolation>()
// Why: the patterns still run on real source, so specifiers survive; the mask only vetoes
// matches whose module keyword is inside a comment or string.
const masked = maskLexicalSpans(code)
const isData = (match: RegExpExecArray | RegExpMatchArray): boolean => {
const keyword = Math.max(match[0].search(/import|export|require/), 0)
return masked[(match.index ?? 0) + keyword] === MASKED
}
for (const pattern of [LITERAL_MODULE_CALL_RE, STATIC_MODULE_SOURCE_RE]) {
pattern.lastIndex = 0
for (const match of code.matchAll(pattern)) {
const specifier = match[2]
if (specifier !== undefined) {
if (specifier !== undefined && !isData(match)) {
collect(violations, classifySpecifier(specifier))
}
}
}
NONLITERAL_MODULE_CALL_RE.lastIndex = 0
for (const match of code.matchAll(NONLITERAL_MODULE_CALL_RE)) {
if (isData(match)) {
continue
}
collect(violations, {
kind: 'nonliteral-module-request',
detail: code