Release v0.4.0

This commit is contained in:
winlifes
2026-06-10 20:26:09 -07:00
parent 03e5ab8dfe
commit 6405dced7d
27 changed files with 1983 additions and 52 deletions
+114
View File
@@ -0,0 +1,114 @@
'use strict';
const path = require('path');
const { isPathInside } = require('./path-safety');
const DELETE_METHOD_PATTERN = /\bfs(?:\s*\.\s*promises)?\s*\.\s*(rm|rmdir|unlink|truncate|rmSync|rmdirSync|unlinkSync|truncateSync)\s*\(/;
const WRITE_STREAM_PATTERN = /\bfs\s*\.\s*(createWriteStream|openSync)\s*\(/;
const SHELL_PATTERN = /require\s*\(\s*['"]child_process['"]\s*\)|\bchild_process\s*\.|\b(exec|execFile|spawn|fork|execSync|execFileSync|spawnSync)\s*\(/;
const WRITE_METHOD_PATTERN = /\bfs(?:\s*\.\s*promises)?\s*\.\s*(writeFile|appendFile|copyFile|cp|rename|mkdir|writeFileSync|appendFileSync|copyFileSync|cpSync|renameSync|mkdirSync)\s*\(/;
const HOME_PATH_PATTERN = /(?:^~(?:\/|\\|$)|\$HOME|%USERPROFILE%|%HOMEPATH%)/i;
const TRAVERSAL_PATTERN = /(^|[\\/])\.\.([\\/]|$)/;
function extractStringLiterals(code) {
const literals = [];
const pattern = /(['"`])((?:\\[\s\S]|(?!\1)[\s\S])*?)\1/g;
let match;
while ((match = pattern.exec(String(code || '')))) {
literals.push(match[2]);
}
return literals;
}
function isAbsoluteLiteral(value) {
return path.isAbsolute(value)
|| path.win32.isAbsolute(value)
|| /^\\\\/.test(value);
}
function isAbsoluteLiteralInsideProject(projectPath, value) {
if (!projectPath) {
return false;
}
if (path.win32.isAbsolute(value)) {
const root = projectPath.replace(/\//g, '\\');
const relative = path.win32.relative(root, value);
return relative === '' || (relative && !relative.startsWith('..') && !path.win32.isAbsolute(relative));
}
if (path.isAbsolute(value)) {
return isPathInside(projectPath, path.resolve(value));
}
return false;
}
function inspectJavascriptSafety(code, options = {}) {
const source = String(code || '');
const projectPath = options.projectPath ? path.resolve(String(options.projectPath)) : '';
const violations = [];
if (DELETE_METHOD_PATTERN.test(source)) {
violations.push('direct fs delete/truncate calls are blocked by default');
}
if (WRITE_STREAM_PATTERN.test(source)) {
violations.push('raw writable file streams are blocked by default');
}
if (SHELL_PATTERN.test(source)) {
violations.push('child_process execution is blocked by default');
}
const hasFileMutation = DELETE_METHOD_PATTERN.test(source)
|| WRITE_METHOD_PATTERN.test(source)
|| WRITE_STREAM_PATTERN.test(source);
if (hasFileMutation && /\bos\s*\.\s*homedir\s*\(/.test(source)) {
violations.push('file mutations derived from os.homedir() are blocked by default');
}
if (hasFileMutation && /\bprocess\s*\.\s*env\s*\.\s*(HOME|USERPROFILE|HOMEPATH|APPDATA|LOCALAPPDATA|TMP|TEMP)\b/.test(source)) {
violations.push('file mutations derived from user/system environment paths are blocked by default');
}
for (const literal of extractStringLiterals(source)) {
if (HOME_PATH_PATTERN.test(literal)) {
violations.push(`user-home path literal is blocked: ${literal}`);
continue;
}
if (TRAVERSAL_PATTERN.test(literal)) {
violations.push(`path traversal literal is blocked: ${literal}`);
continue;
}
if (isAbsoluteLiteral(literal)) {
if (!isAbsoluteLiteralInsideProject(projectPath, literal)) {
violations.push(`absolute path outside the Cocos project is blocked: ${literal}`);
}
}
}
return {
ok: violations.length === 0,
violations: Array.from(new Set(violations)),
};
}
function assertJavascriptSafety(code, options = {}) {
const result = inspectJavascriptSafety(code, options);
if (result.ok) {
return result;
}
throw new Error(
'JavaScript safety checks blocked this code: ' +
`${result.violations.join('; ')}. ` +
'Use project-relative helper/file tools, or pass safety_checks=false only after reviewing the risk.'
);
}
module.exports = {
assertJavascriptSafety,
inspectJavascriptSafety,
};