UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

113 lines (112 loc) • 4.59 kB
import { delimitUntrusted, neutralizeForFence } from "./untrusted-text.js"; import { scrubSecrets, SECRET_PASSES, runRedactionPasses } from "./arg-summary.js"; import { sliceHeadSafe } from "./surrogate-safe-slice.js"; export { summarizeRedactions } from "./arg-summary.js"; const URI_SCHEME = String.raw `[A-Za-z][A-Za-z0-9+.\-_]{0,63}`; const CREDENTIALS_IN_AUTHORITY = new RegExp(String.raw `(${URI_SCHEME}:\/\/)((?:[^/\s"'@]*@)+)`, "gi"); const URL_OF_ANY_PROTOCOL = new RegExp(String.raw `${URI_SCHEME}:\/\/[^\s"'\`]+`, "gi"); const BARE_USERNAME_MAX_LEN = 20; function redactUserinfo(scheme, userinfoWithAt) { const userinfo = userinfoWithAt.slice(0, -1); if (userinfo.includes("@")) return `${scheme}[redacted-credentials]@`; const colonIdx = userinfo.indexOf(":"); if (colonIdx === -1) { return userinfo.length >= BARE_USERNAME_MAX_LEN ? `${scheme}[redacted-credentials]@` : `${scheme}${userinfo}@`; } return `${scheme}${userinfo.slice(0, colonIdx)}:[redacted-credentials]@`; } const SECRET_TIER_PASSES = [ ...SECRET_PASSES, { kind: "url-credentials", confidence: "medium", marker: "[redacted-credentials]", source: "untrusted-egress", re: CREDENTIALS_IN_AUTHORITY, replace: (_m, scheme, userinfo) => redactUserinfo(scheme, userinfo), }, { kind: "signed-url-param", confidence: "high", marker: "[redacted]", source: "untrusted-egress", re: /([?&#](?:sig|signature|sas|code)=)[^&\s"'#]+/gi, replace: "$1[redacted]" }, ]; export function redactSecrets(s, report) { return runRedactionPasses(s, SECRET_TIER_PASSES, report); } const HOST_LEAK_PASSES = [ ...SECRET_TIER_PASSES, { kind: "url", confidence: "medium", marker: "[redacted-url]", source: "untrusted-egress", re: URL_OF_ANY_PROTOCOL, replace: "[redacted-url]" }, { kind: "fs-path", confidence: "low", marker: "[redacted-path]", source: "untrusted-egress", re: /(?:\/[A-Za-z0-9_.-]+){2,}/g, replace: "[redacted-path]" }, ]; export function redactHostLeaks(s, report) { return runRedactionPasses(s, HOST_LEAK_PASSES, report); } const REDACTION_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g; function countRedactionMarkers(s) { return (s.match(REDACTION_MARKER_RE) ?? []).length; } function safeEllipsisTruncate(s, max) { if (max < 1) return ""; const budget = max - 1; let out = ""; for (const codePoint of s) { if (out.length + codePoint.length > budget) break; out += codePoint; } return out + "…"; } function boundedString(value, max, redact) { let s; try { s = typeof value === "string" ? value : JSON.stringify(value) ?? String(value); } catch { s = "[unserializable result]"; } s = redact(s); if (s.length <= max) return s; const totalMarkers = countRedactionMarkers(s); const plainSuffix = (kept) => `…[+${s.length - kept.length} chars]`; const enrichedSuffix = (kept) => { const lost = totalMarkers - countRedactionMarkers(kept); return lost > 0 ? `…[+${s.length - kept.length} chars, ${lost} redaction marker(s) among them]` : plainSuffix(kept); }; const converge = (suffixFor) => { let keep = max; let out = ""; for (let i = 0; i < 4; i++) { const kept = sliceHeadSafe(s, keep); out = `${kept}${suffixFor(kept)}`; if (out.length <= max) return out; if (keep === 0) return undefined; keep = Math.max(0, keep - (out.length - max)); } return out.length <= max ? out : undefined; }; const out = (totalMarkers > 0 ? converge(enrichedSuffix) : undefined) ?? converge(plainSuffix); if (out === undefined) { return safeEllipsisTruncate(s, max); } if (out.length >= s.length) return s; return out; } export function boundedRedactedSummary(value, max) { return boundedString(value, max, redactSecrets); } export function untrustedEgressForHuman(value, opts) { const source = boundedString(value, Number.POSITIVE_INFINITY, redactHostLeaks); let budget = opts.max; let bounded = boundedString(source, budget, (s) => s); for (let i = 0; i < 4 && budget > 0; i++) { const overflow = [...neutralizeForFence(bounded)].length - opts.max; if (overflow <= 0) break; budget = Math.max(0, budget - overflow); bounded = boundedString(source, budget, (s) => s); } return delimitUntrusted(opts.label, bounded, opts.max); }