UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

49 lines (48 loc) • 2.36 kB
import { detectSecret, MEMORY_WRAPPER_TAGS } from "../memory.js"; import { defuseFenceMarkers, sanitizeUntrustedText } from "../untrusted-text.js"; export const MEMORY_FILENAME_SEGMENT_RE = /^[A-Za-z0-9._-]+$/; export function scanMemoryFileName(relPath) { for (const segment of relPath.split("/")) { if (segment === "") continue; if (!MEMORY_FILENAME_SEGMENT_RE.test(segment)) { return { code: "filename", reason: `file name contains characters outside the memory whitelist (allowed: letters, digits, ".", "_", "-" per path segment)`, }; } } return undefined; } export function scanMemoryWrite(text, opts = {}) { const findings = []; if (opts.maxBytes !== undefined) { const size = Buffer.byteLength(text, "utf8"); if (size > opts.maxBytes) { findings.push({ code: "too_large", reason: `${size} bytes — over the ${opts.maxBytes}-byte cap; split or trim it (rejected, NOT truncated)` }); } } const secretLabel = detectSecret(text); if (secretLabel !== null) { findings.push({ code: "secret", reason: `detected ${secretLabel}` }); } if (sanitizeUntrustedText(text, [...MEMORY_WRAPPER_TAGS]) !== text) { findings.push({ code: "injection", reason: "contains a framing break-out tag (<system-reminder>/<user_memory> family)" }); } else if (/^[ \t]*(?:<{3,}|>{3,})/m.test(text) && defuseFenceMarkers(text) !== text) { findings.push({ code: "injection", reason: "contains an untrusted-fence sentinel at line start (a run of 3+ '<' or '>')" }); } return findings; } export function scanRemediation(code) { switch (code) { case "secret": return "Remove the secret value — store a POINTER to where it lives (env var name, vault path) instead of the value itself."; case "injection": return "Remove the markup: memory files must not contain <system-reminder>/<user_memory> tags or runs of 3+ '<'/'>' (they would forge the injection framing when this memory is loaded)."; case "filename": return 'Rename the file using only letters, digits, ".", "_" and "-" in each path segment.'; case "too_large": return "Split the content into smaller topic files, or trim it."; } }