Files
memind/mindspace-browser-storage-policy.mjs
T

61 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}