@sema-agent/core
Version:
Stateless, task-oriented AI agent core
230 lines (229 loc) • 8.72 kB
JavaScript
import { delimitUntrusted, inlineUntrusted, neutralizeForFence } from "./untrusted-text.js";
import { egressPasses, runRedactionPasses } from "./redaction-passes.js";
import { CREDENTIAL_VOCABULARY, egressPassGroups, isSensitiveRecordKey, URI_SCHEME_SOURCE } from "./credential-vocabulary.js";
import { isErrorLike, readMember, thrownText } from "./thrown-value.js";
import { sliceHeadSafe } from "./surrogate-safe-slice.js";
export { summarizeRedactions } from "./redaction-passes.js";
const URL_OF_ANY_PROTOCOL = new RegExp(String.raw `${URI_SCHEME_SOURCE}:\/\/[^\s"'\`]+`, "gi");
const BARE_USERNAME_MAX_LEN = 20;
function redactUserinfo(scheme, userinfoWithAt) {
const userinfo = userinfoWithAt.slice(0, -1);
if (CREDENTIAL_VOCABULARY.policy.urlUserinfo === "whole" || 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 = egressPasses(egressPassGroups(CREDENTIAL_VOCABULARY), {
"url-credentials": (_m, scheme, userinfo) => redactUserinfo(scheme, userinfo),
});
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]" },
];
const TIER_PASSES = { "same-principal": SECRET_TIER_PASSES, human: HOST_LEAK_PASSES };
export function egressText(tier, text, report) {
return runRedactionPasses(text, TIER_PASSES[tier], report);
}
export function egressThrownText(tier, err) {
return egressText(tier, thrownText(err));
}
export function redactSecrets(s, report) {
return egressText("same-principal", s, report);
}
export function extensionWords(s, max) {
return inlineUntrusted(egressText("same-principal", s), max);
}
export function redactHostLeaks(s, report) {
return egressText("human", s, report);
}
const VALUE_DEPTH_MAX = 32;
const VALUE_MEMBERS_MAX = 2_000;
const WHOLE = "[redacted]";
export function egressValue(tier, value) {
const snapshots = new Map();
let budget = VALUE_MEMBERS_MAX;
const ownKeys = (o, all) => {
try {
return all ? Reflect.ownKeys(o) : Object.keys(o);
}
catch {
return undefined;
}
};
const prototypeOf = (o) => {
try {
return Object.getPrototypeOf(o);
}
catch {
return Object.prototype;
}
};
const readOwn = (o, key) => {
try {
const d = Reflect.getOwnPropertyDescriptor(o, key);
if (d === undefined)
return { value: undefined, enumerable: true };
return { value: "value" in d ? d.value : d.get?.call(o), enumerable: d.enumerable ?? true };
}
catch {
return { value: undefined, enumerable: true };
}
};
const member = (read, depth, name) => {
const out = pass(read, depth + 1, name);
return typeof out === "string" && isSensitiveRecordKey(name) ? WHOLE : out;
};
const fill = (o, depth, keys, snapshot) => {
snapshots.set(o, snapshot);
for (const key of keys ?? []) {
if (budget-- <= 0)
break;
const read = readOwn(o, key);
const name = typeof key === "string" ? egressText(tier, key) : key;
Object.defineProperty(snapshot, name, { value: member(read.value, depth, typeof key === "string" ? key : ""), enumerable: read.enumerable, writable: true, configurable: true });
}
return snapshot;
};
const errorSnapshot = (o, depth) => {
const snapshot = new Error();
for (const key of Reflect.ownKeys(snapshot))
Reflect.deleteProperty(snapshot, key);
fill(o, depth, ownKeys(o, true), snapshot);
for (const key of ["name", "message", "code", "stack"]) {
if (Object.prototype.hasOwnProperty.call(snapshot, key))
continue;
const read = readMember(o, key);
if (read !== undefined && typeof read !== "function")
Object.defineProperty(snapshot, key, { value: member(read, depth, key), enumerable: false, writable: true, configurable: true });
}
return snapshot;
};
function pass(v, depth, key) {
if (typeof v === "string")
return egressText(tier, v);
if (typeof v === "function")
return undefined;
if (typeof v !== "object" || v === null)
return v;
const known = snapshots.get(v);
if (known !== undefined)
return known;
if (depth >= VALUE_DEPTH_MAX)
return WHOLE;
if (isErrorLike(v))
return errorSnapshot(v, depth);
const toJSON = readMember(v, "toJSON");
if (typeof toJSON === "function") {
let reading;
try {
reading = toJSON.call(v, key);
}
catch {
return WHOLE;
}
if (reading !== v) {
const out = pass(reading, depth + 1, key);
snapshots.set(v, out);
return out;
}
}
const array = (() => {
try {
return Array.isArray(v);
}
catch {
return false;
}
})();
return fill(v, depth, ownKeys(v, false), array ? [] : prototypeOf(v) === null ? Object.create(null) : {});
}
return pass(value, 0, "");
}
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, (s) => egressText("same-principal", s));
}
export function untrustedEgressForHuman(value, opts) {
const source = boundedString(value, Number.POSITIVE_INFINITY, (s) => egressText("human", s));
const probe = (budget) => {
const bounded = boundedString(source, budget, (s) => s);
return { bounded, overflow: [...neutralizeForFence(bounded)].length - opts.max };
};
let chosen = probe(opts.max);
if (chosen.overflow > 0) {
let fits;
let lo = 0;
let hi = opts.max;
while (hi - lo > 1) {
const mid = lo + Math.floor((hi - lo) / 2);
const p = probe(mid);
if (p.overflow <= 0) {
lo = mid;
fits = p;
}
else {
hi = mid;
}
}
chosen = fits ?? probe(lo);
}
return delimitUntrusted(opts.label, chosen.bounded, opts.max);
}