115 lines · 4.3 KB
import { waitUntil } from "cloudflare:workers";
import { accountDb } from "../scoped-db";
import { randomId } from "../crypto";
import { HttpError } from "../errors";
import { enforceRateLimit, limitHeavy } from "../limits";
import { RPC, RpcError } from "./protocol";
import type { McpCaller } from "./protocol";
import { recordToolUsage } from "./usage";

const MAX_AUDIT_STRING = 200;
const MAX_AUDIT_JSON = 4000;
const MAX_AUDIT_DEPTH = 4;
/**
 * Argument keys whose values are user content or credentials: logged as a length and hash, never verbatim.
 * Covers file bodies, commit messages, CI `inputs` (often secrets), scratch-site `files` and claim notes.
 */
const REDACTED_KEYS = new Set([
  "content",
  "token",
  "resolutions",
  "body",
  "inputs",
  "files",
  "message",
  "note",
  "title",
  "description",
  "text",
  "secret",
  "value",
  "env",
]);

function redact(value: unknown, depth = 0): unknown {
  if (depth > MAX_AUDIT_DEPTH) return "…";
  if (typeof value === "string") return value.length > MAX_AUDIT_STRING ? `<${value.length} chars>` : value;
  if (Array.isArray(value)) return value.slice(0, 20).map((item) => redact(item, depth + 1));
  if (typeof value !== "object" || value === null) return value;
  const out: Record<string, unknown> = {};
  for (const [key, item] of Object.entries(value)) {