feat(mindspace): enforce PostgreSQL user data delivery

This commit is contained in:
john
2026-07-13 15:29:29 +08:00
parent b33c943b69
commit a6620fb719
57 changed files with 2779 additions and 164 deletions
+60
View File
@@ -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;
}