Files
memind/wechat/verify/page-artifact.mjs
T
john 760a1760ae
Memind CI / Test, build, and release guards (push) Successful in 6m58s
fix(wechat): enforce current-turn image paths in page delivery
Materialize wechat-mp uploads into dated public/images paths during vision
preprocessing and fail closed when delivered HTML references stale workspace
images instead of the current turn allowlist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 12:54:46 +08:00

143 lines
4.3 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { verifyHtmlImageSourcesAllowed } from '../../chat-image-materialize.mjs';
const STUB_MARKERS = ['临时补出', '服务号兜底', '服务号自动补出简版页面'];
export function resolveArtifactLocalPath(artifact, publishDir = '') {
const localPath = String(artifact?.localPath ?? '').trim();
if (localPath) return localPath;
const relativePath = String(artifact?.relativePath ?? '').trim();
const root = String(publishDir ?? '').trim();
if (!relativePath || !root) return '';
const resolved = path.resolve(root, relativePath);
const normalizedRoot = path.resolve(root);
if (
resolved !== normalizedRoot &&
!resolved.startsWith(`${normalizedRoot}${path.sep}`)
) {
return '';
}
return resolved;
}
export function isStubPublicHtmlContent(content) {
const value = String(content ?? '');
return STUB_MARKERS.some((marker) => value.includes(marker));
}
export function artifactFileExists(artifact, { publishDir = '' } = {}) {
const localPath = resolveArtifactLocalPath(artifact, publishDir);
if (localPath) {
try {
return fs.existsSync(localPath) && fs.statSync(localPath).isFile();
} catch {
return false;
}
}
if (typeof artifact?.exists === 'boolean') {
return artifact.exists;
}
return false;
}
export function isStubPublicHtmlArtifact(artifact, { publishDir = '' } = {}) {
if (typeof artifact?.isStub === 'boolean') {
return artifact.isStub;
}
const localPath = resolveArtifactLocalPath(artifact, publishDir);
if (!localPath) return false;
try {
return isStubPublicHtmlContent(fs.readFileSync(localPath, 'utf8'));
} catch {
return false;
}
}
/** Real HTML artifacts only — never fall back to stub placeholders. */
export function filterSendableHtmlArtifacts(artifacts = [], options = {}) {
return artifacts.filter(
(artifact) =>
artifactFileExists(artifact, options) &&
!isStubPublicHtmlArtifact(artifact, options),
);
}
export function selectSendableHtmlArtifacts({
verifiedArtifacts = [],
confirmedArtifacts = [],
publishDir = '',
} = {}) {
const options = { publishDir };
const candidates = verifiedArtifacts.length > 0 ? verifiedArtifacts : confirmedArtifacts;
return filterSendableHtmlArtifacts(candidates, options);
}
export function verifyPageArtifactContent(artifact, { minBytes = 512, publishDir = '' } = {}) {
const localPath = resolveArtifactLocalPath(artifact, publishDir);
const resolvedArtifact = localPath ? { ...artifact, localPath } : artifact;
if (!artifactFileExists(resolvedArtifact, { publishDir })) {
return { ok: false, reason: 'missing_file' };
}
if (isStubPublicHtmlArtifact(resolvedArtifact, { publishDir })) {
return { ok: false, reason: 'stub_placeholder' };
}
if (!localPath) {
if (Number(artifact?.sizeBytes ?? 0) < minBytes) {
return {
ok: false,
reason: 'too_small',
};
}
if (artifact?.isHtmlDocument !== true) {
return {
ok: false,
reason: 'not_html_document',
};
}
return { ok: true, reason: null };
}
const size = fs.statSync(localPath).size;
if (size < minBytes) {
return { ok: false, reason: 'too_small' };
}
const content = fs.readFileSync(localPath, 'utf8');
if (!/<(?:html|body|main|article)\b/i.test(content)) {
return { ok: false, reason: 'not_html_document' };
}
return { ok: true, reason: null };
}
export function verifyPageArtifactImageSources(
artifact,
{
publishDir = '',
allowedImageEmbedKeys = null,
} = {},
) {
const allowed = allowedImageEmbedKeys instanceof Set
? allowedImageEmbedKeys
: Array.isArray(allowedImageEmbedKeys)
? new Set(allowedImageEmbedKeys)
: null;
if (!allowed || allowed.size === 0) {
return { ok: true, reason: null, offendingKeys: [] };
}
const localPath = resolveArtifactLocalPath(artifact, publishDir);
if (!localPath) {
return { ok: false, reason: 'missing_file', offendingKeys: [] };
}
let content = '';
try {
content = fs.readFileSync(localPath, 'utf8');
} catch {
return { ok: false, reason: 'missing_file', offendingKeys: [] };
}
const verification = verifyHtmlImageSourcesAllowed(content, allowed);
return {
ok: verification.ok,
reason: verification.reason,
offendingKeys: verification.offendingKeys ?? [],
};
}