feat(mindspace): enforce PostgreSQL user data delivery
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const AGENT_PAGE_CODE_EXTENSIONS = new Set(['.html', '.htm', '.js', '.mjs', '.jsx', '.ts', '.tsx']);
|
||||
const PROHIBITED_BROWSER_STORAGE_PATTERNS = [
|
||||
{ api: 'localStorage', pattern: /\blocalStorage\b/i },
|
||||
{ api: 'sessionStorage', pattern: /\bsessionStorage\b/i },
|
||||
{ api: 'IndexedDB', pattern: /\b(?:indexedDB|IDBDatabase|IDBObjectStore|IDBTransaction)\b/i },
|
||||
];
|
||||
|
||||
export function isAgentPageCodePath(relativePath) {
|
||||
return AGENT_PAGE_CODE_EXTENSIONS.has(path.extname(String(relativePath ?? '')).toLowerCase());
|
||||
}
|
||||
|
||||
export function detectProhibitedBrowserStorage(content, { relativePath = '' } = {}) {
|
||||
if (!isAgentPageCodePath(relativePath)) return [];
|
||||
let source = String(content ?? '');
|
||||
// Fold adjacent string literals so computed-property obfuscation such as
|
||||
// window['local' + 'Storage'] cannot bypass the delivery policy.
|
||||
for (let pass = 0; pass < 8; pass += 1) {
|
||||
const folded = source.replace(
|
||||
/(['"])([^'"\\]*)\1\s*\+\s*(['"])([^'"\\]*)\3/g,
|
||||
(_match, quote, left, _rightQuote, right) => `${quote}${left}${right}${quote}`,
|
||||
);
|
||||
if (folded === source) break;
|
||||
source = folded;
|
||||
}
|
||||
return PROHIBITED_BROWSER_STORAGE_PATTERNS
|
||||
.filter(({ pattern }) => pattern.test(source))
|
||||
.map(({ api }) => api);
|
||||
}
|
||||
|
||||
export function assertNoProhibitedBrowserStorage(content, { relativePath = '' } = {}) {
|
||||
const APIs = detectProhibitedBrowserStorage(content, { relativePath });
|
||||
if (APIs.length === 0) return;
|
||||
const error = new Error(
|
||||
`${relativePath || '页面代码'} 禁止使用 ${APIs.join(', ')} 保存数据。`
|
||||
+ '用户业务数据必须通过 private_data_* 工具写入该用户专属 PostgreSQL schema,'
|
||||
+ 'HTML 必须通过 Page Data API 访问;请改写后再保存。',
|
||||
);
|
||||
error.code = 'BROWSER_STORAGE_FORBIDDEN';
|
||||
error.apis = APIs;
|
||||
error.relativePath = relativePath || null;
|
||||
throw error;
|
||||
}
|
||||
|
||||
export function scanWorkspaceFilesForProhibitedBrowserStorage({ publishDir, relativePaths = [] } = {}) {
|
||||
const root = path.resolve(String(publishDir ?? ''));
|
||||
if (!root) return [];
|
||||
const violations = [];
|
||||
for (const relativePath of new Set(relativePaths.map((item) => String(item ?? '').trim()).filter(Boolean))) {
|
||||
if (!isAgentPageCodePath(relativePath)) continue;
|
||||
const absolutePath = path.resolve(root, relativePath);
|
||||
if (absolutePath !== root && !absolutePath.startsWith(`${root}${path.sep}`)) continue;
|
||||
if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) continue;
|
||||
const apis = detectProhibitedBrowserStorage(fs.readFileSync(absolutePath, 'utf8'), { relativePath });
|
||||
if (apis.length > 0) violations.push({ relativePath, apis });
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
Reference in New Issue
Block a user