@sema-agent/core
Version:
Stateless, task-oriented AI agent core
288 lines (287 loc) • 12.5 kB
JavaScript
import { writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
const ACTIVITY_ARG_MAX = 80;
const SENSITIVE_KEY = /token|secret|key|password|passwd|credential|auth/i;
export function summarizeRedactions(findings) {
if (findings.length === 0)
return "no redactions";
const counts = new Map();
for (const f of findings) {
const label = `${f.kind} (${f.confidence})`;
counts.set(label, (counts.get(label) ?? 0) + 1);
}
const parts = [...counts].map(([label, n]) => `${label} x${n}`);
return `${findings.length} redaction${findings.length === 1 ? "" : "s"}: ${parts.join(", ")}`;
}
function mapBackOnePass(edits, pos) {
let delta = 0;
for (const e of edits) {
const outStart = e.at + delta;
if (pos < outStart)
return pos - delta;
const outEnd = outStart + e.insertedLen;
if (pos < outEnd)
return e.at;
delta += e.insertedLen - e.removedLen;
}
return pos - delta;
}
const PREEXISTING_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g;
const FORMAT_CHAR_RE = /\p{Cf}/u;
const MAX_FORMAT_SLOT_SCANS = 64;
const REDACTED_WHOLE = "[redacted]";
function applyPasses(input, passes, report, toOriginal) {
const batches = [];
let cur = input;
for (const pass of passes) {
const edits = [];
cur = cur.replace(pass.re, (...args) => {
const match = args[0];
const offset = args[args.length - 2];
const groups = args.slice(1, -2);
const inserted = typeof pass.replace === "string"
? pass.replace.replace(/\$(\d)/g, (_m, d) => groups[Number(d) - 1] ?? "")
: pass.replace(match, ...groups);
if (inserted !== match) {
if (report !== undefined) {
let s0 = offset;
let e0 = offset + match.length;
for (let i = batches.length - 1; i >= 0; i--) {
s0 = mapBackOnePass(batches[i], s0);
e0 = mapBackOnePass(batches[i], e0);
}
report.findings.push({
kind: pass.kind,
confidence: pass.confidence,
span: [toOriginal(s0), toOriginal(e0)],
marker: pass.marker,
source: pass.source,
});
}
edits.push({ at: offset, removedLen: match.length, insertedLen: inserted.length, inserted });
}
return inserted;
});
batches.push(edits);
}
return { out: cur, batches };
}
function replacedSpans(batches) {
const spans = [];
for (let j = 0; j < batches.length; j++) {
for (const e of batches[j]) {
let s0 = e.at;
let e0 = e.at + e.removedLen;
for (let i = j - 1; i >= 0; i--) {
s0 = mapBackOnePass(batches[i], s0);
e0 = mapBackOnePass(batches[i], e0);
}
if (e0 - s0 !== e.removedLen)
return undefined;
spans.push({ start: s0, end: e0, inserted: e.inserted, pass: j });
}
}
spans.sort((a, b) => a.start - b.start);
for (let i = 1; i < spans.length; i++)
if (spans[i].start < spans[i - 1].end)
return undefined;
return spans;
}
export function runRedactionPasses(input, passes, report) {
if (report !== undefined && report.preexistingMarkers === undefined) {
report.preexistingMarkers = input.match(PREEXISTING_MARKER_RE)?.length ?? 0;
}
if (!FORMAT_CHAR_RE.test(input))
return applyPasses(input, passes, report, (pos) => pos).out;
const viewChars = [];
const viewToOriginal = [];
const formatAtSlot = [];
const charStartOffsets = [];
{
let orig = 0;
let viewOff = 0;
let slotBuf = "";
for (const ch of input) {
if (FORMAT_CHAR_RE.test(ch)) {
slotBuf += ch;
}
else {
formatAtSlot.push(slotBuf);
slotBuf = "";
viewToOriginal.push(orig);
charStartOffsets.push(viewOff);
viewChars.push(ch);
viewOff += ch.length;
}
orig += ch.length;
}
formatAtSlot.push(slotBuf);
viewToOriginal.push(input.length);
charStartOffsets.push(viewOff);
}
const view = viewChars.join("");
const offsetToCharIndex = new Map();
charStartOffsets.forEach((off, idx) => offsetToCharIndex.set(off, idx));
const toOriginal = (pos) => {
const idx = offsetToCharIndex.get(pos);
return idx === undefined ? input.length : viewToOriginal[idx];
};
const { out, batches } = applyPasses(view, passes, report, toOriginal);
const viewSpans = replacedSpans(batches);
if (viewSpans === undefined)
return out;
const spans = viewSpans.map((sp) => {
const startIdx = offsetToCharIndex.get(sp.start) ?? viewChars.length;
const endIdx = offsetToCharIndex.get(sp.end) ?? viewChars.length;
const lastIdx = endIdx - 1;
const endOrig = lastIdx >= startIdx && lastIdx < viewChars.length ? viewToOriginal[lastIdx] + viewChars[lastIdx].length : viewToOriginal[startIdx];
return { start: viewToOriginal[startIdx], end: endOrig, inserted: sp.inserted };
});
const viewOffsetToOriginal = (viewStart, viewEnd) => {
const startIdx = offsetToCharIndex.get(viewStart) ?? viewChars.length;
const endIdx = offsetToCharIndex.get(viewEnd) ?? viewChars.length;
const lastIdx = endIdx - 1;
const endOrig = lastIdx >= startIdx && lastIdx < viewChars.length ? viewToOriginal[lastIdx] + viewChars[lastIdx].length : viewToOriginal[startIdx];
return { start: viewToOriginal[startIdx], end: endOrig };
};
const cutOffsets = [];
for (let k = 1; k < viewChars.length; k++)
if (formatAtSlot[k] !== "")
cutOffsets.push(charStartOffsets[k]);
if (cutOffsets.length > MAX_FORMAT_SLOT_SCANS)
return REDACTED_WHOLE;
const extra = [];
const rawSpans = replacedSpans(applyPasses(input, passes, undefined, (pos) => pos).batches);
if (rawSpans !== undefined)
extra.push(...rawSpans);
for (const cut of cutOffsets) {
const cutSpans = replacedSpans(applyPasses(view.slice(cut), passes, undefined, (pos) => pos).batches);
if (cutSpans === undefined)
continue;
for (const c of cutSpans) {
const o = viewOffsetToOriginal(c.start + cut, c.end + cut);
extra.push({ start: o.start, end: o.end, inserted: c.inserted, pass: c.pass });
}
}
extra.sort((a, b) => a.start - b.start);
for (const r of extra) {
const overlapping = spans.find((v) => r.start < v.end && v.start < r.end);
if (overlapping !== undefined) {
if (r.start < overlapping.start)
overlapping.start = r.start;
if (r.end > overlapping.end)
overlapping.end = r.end;
continue;
}
spans.push({ start: r.start, end: r.end, inserted: r.inserted });
if (report !== undefined) {
const pass = passes[r.pass];
report.findings.push({ kind: pass.kind, confidence: pass.confidence, span: [r.start, r.end], marker: pass.marker, source: pass.source });
}
spans.sort((a, b) => a.start - b.start);
}
for (let i = 1; i < spans.length;) {
if (spans[i].start < spans[i - 1].end) {
spans[i - 1].end = Math.max(spans[i - 1].end, spans[i].end);
spans.splice(i, 1);
}
else
i++;
}
let result = "";
let cursor = 0;
for (const sp of spans) {
result += input.slice(cursor, sp.start) + sp.inserted;
cursor = sp.end;
}
result += input.slice(cursor);
return result;
}
export const SECRET_PASSES = [
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])(?:sk|pk|rk|gh[opsur]|github_pat)[-_][A-Za-z0-9_-]{8,}/g, replace: "[redacted]" },
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{20,}/g, replace: "[redacted]" },
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])xox[baprs]-[A-Za-z0-9-]{10,}/g, replace: "[redacted]" },
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{12,}/g, replace: "[redacted]" },
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{20,}/g, replace: "[redacted]" },
{ kind: "jwt", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])eyJ[A-Za-z0-9._-]{20,}/g, replace: "[redacted]" },
{ kind: "private-key-block", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /-----BEGIN[ A-Z0-9]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END[ A-Z0-9]*PRIVATE KEY(?: BLOCK)?-----|$)/g, replace: "[redacted]" },
{ kind: "bearer-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])Bearer\s+[A-Za-z0-9._~+/-]{8,}=*/gi, replace: "[redacted]" },
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(AccountKey=)[A-Za-z0-9+/]{40,}={0,2}/g, replace: "$1[redacted]" },
{
kind: "keyword-value",
confidence: "medium",
marker: "[redacted]",
source: "arg-summary",
re: /(?<![A-Za-z0-9])((?:token|secret|key|password|passwd|credential|authorization)["']?\s*[=:]\s*["']?)[^\s"';|&]{4,}/gi,
replace: "$1[redacted]",
},
];
export function scrubSecrets(s, report) {
return runRedactionPasses(s, SECRET_PASSES, report);
}
function truncCodePoints(s) {
const cp = Array.from(s);
return cp.length > ACTIVITY_ARG_MAX ? cp.slice(0, ACTIVITY_ARG_MAX).join("") + "…" : s;
}
const basename = (p) => p.split(/[/\\]/).filter((x) => x.length > 0).pop() ?? p;
export function primaryActivityArg(args) {
if (!args || typeof args !== "object")
return undefined;
const a = args;
const str = (k) => typeof a[k] === "string" && a[k].length > 0 ? a[k] : undefined;
let val;
const command = str("command");
const path = str("file_path") ?? str("filePath") ?? str("path") ?? str("notebook_path");
const url = str("url");
if (command !== undefined) {
const tokens = command.trim().split(/\s+/);
let i = 0;
while (i < tokens.length && writtenAssignmentPrefix(tokens[i]) !== undefined) {
if (/["'\\]/.test(tokens[i]))
return undefined;
i++;
}
if (i >= tokens.length)
return undefined;
const name = basename(tokens[i]);
val = tokens.length > i + 1 ? `${name} …` : name;
}
else if (path !== undefined) {
val = basename(path);
}
else if (url !== undefined) {
const parsed = (() => { try {
return new URL(url);
}
catch {
return undefined;
} })();
if (parsed !== undefined && parsed.host !== "") {
val = `${parsed.origin}${parsed.pathname}`;
}
else {
const head = url.split(/[?#]/, 1)[0] ?? url;
const sep = head.indexOf("://");
const scheme = sep > 0 && /^[a-z][a-z0-9+.-]*$/i.test(head.slice(0, sep)) ? head.slice(0, sep + 3) : "";
const rest = head.slice(scheme.length);
const at = rest.lastIndexOf("@");
val = scheme + (at === -1 ? rest : rest.slice(at + 1));
}
}
else {
val = str("pattern") ?? str("query") ?? str("description");
if (val === undefined) {
for (const k of Object.keys(a)) {
if (SENSITIVE_KEY.test(k))
continue;
const v = a[k];
if (typeof v === "string" && v.length > 0) {
val = v;
break;
}
}
}
}
if (val === undefined)
return undefined;
return truncCodePoints(scrubSecrets(val));
}