74 lines
4.2 KiB
JavaScript
74 lines
4.2 KiB
JavaScript
import { ALLOWED_OPERATIONS, ALLOWED_PATHS } from "./provenance.js";
|
|
|
|
const DEFAULT_READ_ONLY_TOOLS = Object.freeze([
|
|
"read", "grep", "glob", "find", "ls", "web_search", "web_fetch",
|
|
"memory_search", "sessions_list", "sessions_history", "session_status",
|
|
]);
|
|
const GENERIC_MUTATION_TOOLS = new Set([
|
|
"exec", "shell", "bash", "write", "edit", "apply_patch", "file_write",
|
|
"computer", "process", "terminal",
|
|
]);
|
|
|
|
function strings(value) {
|
|
return Array.isArray(value)
|
|
? value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim())
|
|
: [];
|
|
}
|
|
|
|
export function resolveSenderId(ctx = {}) {
|
|
const direct = typeof ctx.senderId === "string" ? ctx.senderId.trim() : "";
|
|
const nested = typeof ctx.channelContext?.sender?.id === "string" ? ctx.channelContext.sender.id.trim() : "";
|
|
return direct || nested || undefined;
|
|
}
|
|
|
|
export function classifyRun(event = {}, ctx = {}, config = {}) {
|
|
const senderId = resolveSenderId({ ...ctx, senderId: event.senderId ?? ctx.senderId });
|
|
const trusted = new Set(strings(config.trustedSenderIds));
|
|
const blocked = new Set(strings(config.blockedSenderIds));
|
|
if (typeof ctx.jobId === "string" && ctx.jobId) return { trusted: false, origin: "automation", reason: "job_id_present" };
|
|
if (!senderId) return { trusted: false, origin: "system_or_unknown", reason: "sender_id_missing" };
|
|
if (blocked.has(senderId)) return { trusted: false, origin: "blocked_sender", reason: "sender_blocked", senderId };
|
|
if (config.requireExplicitTrustedSenders !== false && !trusted.has(senderId)) {
|
|
return { trusted: false, origin: "untrusted_sender", reason: "sender_not_allowlisted", senderId };
|
|
}
|
|
if (config.requireSenderIsOwner !== false && event.senderIsOwner !== true) {
|
|
return { trusted: false, origin: "non_owner", reason: "sender_not_owner", senderId };
|
|
}
|
|
return { trusted: true, origin: "authenticated_owner", reason: "owner_verified", senderId };
|
|
}
|
|
|
|
export function runKey(event = {}, ctx = {}) {
|
|
const key = event.runId || ctx.runId;
|
|
return typeof key === "string" && key.trim() ? key.trim() : undefined;
|
|
}
|
|
|
|
export function decideToolCall(toolName, runTrust, config = {}) {
|
|
const name = typeof toolName === "string" ? toolName.trim().toLowerCase() : "";
|
|
if (!name) return { action: "block", code: "tool_name_missing" };
|
|
if (GENERIC_MUTATION_TOOLS.has(name)) return { action: "block", code: "dedicated_writer_required" };
|
|
const configured = strings(config.readOnlyTools).map((item) => item.toLowerCase());
|
|
const readOnly = new Set(configured.length ? configured : DEFAULT_READ_ONLY_TOOLS);
|
|
if (readOnly.has(name)) return { action: "allow", code: "read_only" };
|
|
if (!runTrust?.trusted) return { action: "block", code: runTrust?.reason || "run_provenance_missing" };
|
|
if (name === "aios_evidence_write") return { action: "verify_provenance", code: "dedicated_writer" };
|
|
if (config.requireApprovalForHumanSideEffects === false) return { action: "allow", code: "trusted_side_effect" };
|
|
return { action: "approval", code: "human_side_effect" };
|
|
}
|
|
|
|
export function validateWriterParams(params = {}, payload) {
|
|
const keys = Object.keys(params).sort();
|
|
const expected = ["document", "operation", "path", "provenance_id"];
|
|
if (keys.length !== expected.length || expected.some((key) => !keys.includes(key))) return { ok: false, code: "tool_params_schema_mismatch" };
|
|
if (params.provenance_id !== payload.provenance_id) return { ok: false, code: "provenance_id_mismatch" };
|
|
if (!ALLOWED_PATHS.includes(params.path)) return { ok: false, code: "path_not_allowed" };
|
|
if (params.operation !== "create" && params.operation !== "update") return { ok: false, code: "operation_not_allowed" };
|
|
if (!payload.allowed_paths.includes(params.path)) return { ok: false, code: "path_not_authorized" };
|
|
for (const operation of [params.operation, "git add", "git commit", "normal push current task branch"]) {
|
|
if (!payload.allowed_operations.includes(operation)) return { ok: false, code: "operation_not_authorized" };
|
|
}
|
|
let serialized;
|
|
try { serialized = JSON.stringify(params.document); } catch { return { ok: false, code: "document_not_json" }; }
|
|
if (serialized === undefined || Buffer.byteLength(serialized) > 256 * 1024) return { ok: false, code: "document_invalid_or_too_large" };
|
|
return { ok: true };
|
|
}
|