@runpod/mcp-server 4.9.0 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  // src/alp/scrub.ts
2
- var SCRUB_VERSION = 5;
2
+ var SCRUB_VERSION = 6;
3
3
  var MARKER = /^["']?\[redacted:[a-z_]+\]["']?$/;
4
4
  var CREDENTIAL_PATTERNS = [
5
5
  // Runpod API keys.
@@ -187,6 +187,22 @@ function redactAssignments(text) {
187
187
  out += text.slice(copiedTo);
188
188
  return { text: out, redactions };
189
189
  }
190
+ var EMAIL = /(?<![\p{L}\p{M}\p{N}_.%+-])([\p{L}\p{M}\p{N}_.%+-]{1,64})@((?:[\p{L}\p{M}\p{N}-]{1,63}\.){1,8}[\p{L}\p{M}]{2,63})(?![\p{L}\p{M}\p{N}])/gu;
191
+ var RUNPOD_SSH_HOST = "ssh.runpod.io";
192
+ function redactEmails(text) {
193
+ let redactions = 0;
194
+ const out = text.replace(
195
+ EMAIL,
196
+ (address, local, domain, offset) => {
197
+ const next = text[offset + address.length];
198
+ if (domain.toLowerCase() === RUNPOD_SSH_HOST || local === "git" && (next === ":" || next === "/"))
199
+ return address;
200
+ redactions++;
201
+ return "[redacted:email]";
202
+ }
203
+ );
204
+ return { text: out, redactions };
205
+ }
190
206
  function scrub(text) {
191
207
  let out = text;
192
208
  let redactions = 0;
@@ -194,7 +210,8 @@ function scrub(text) {
194
210
  redactAnchoredCredentials,
195
211
  redactHeaderValues,
196
212
  redactQuerySecrets,
197
- redactAssignments
213
+ redactAssignments,
214
+ redactEmails
198
215
  ]) {
199
216
  const result = stage(out);
200
217
  out = result.text;
@@ -233,4 +250,4 @@ export {
233
250
  SCRUB_VERSION,
234
251
  scrubSubmission
235
252
  };
236
- //# sourceMappingURL=chunk-H3NZYQIB.mjs.map
253
+ //# sourceMappingURL=chunk-WWXM5EES.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/alp/scrub.ts"],"sourcesContent":["// Best-effort secret redaction before forwarding to storage and again at the\n// Convex write boundary. This catches recognizable tokens, sensitive config\n// assignments and email addresses; arbitrary unlabeled secrets cannot reliably\n// be identified.\n//\n// Five stages, run in this order, each a pure function with its own table\n// of rules and its own tests:\n//\n// A. anchored credentials — shapes recognizable on their own (rpa_, JWT...)\n// B. sensitive headers — the WHOLE value of Authorization/Cookie/...\n// C. URL query secrets — api_key/token/key/secret parameters\n// D. config assignments — `key = value` / `key: value` by key name\n// E. email addresses — personal data, not a credential\n//\n// Specific before broad is the invariant. Several stage-A rules identify a\n// credential by the word in front of it (`Bearer <opaque>`), and stage D stops\n// an unquoted value at the first space or semicolon — so run in the other\n// order, C consumes \"Bearer\", destroys the anchor, and leaves the secret in\n// plaintext looking redacted. Stage B exists because header values are\n// DEFINED by containing spaces and semicolons (`Basic <cred>`, `a=1; b=2`),\n// which is exactly the shape the token-level matcher cannot hold whole.\n\nexport interface ScrubResult {\n text: string;\n /** How many redactions fired — a redaction rate is itself a metric. */\n redactions: number;\n}\n\n// Bump when any stage's rules change so stored rows record which pass they got.\nexport const SCRUB_VERSION = 6;\n\nconst MARKER = /^[\"']?\\[redacted:[a-z_]+\\][\"']?$/;\n\n// ---- Stage A: anchored credential shapes --------------------------------\n\nconst CREDENTIAL_PATTERNS: Array<{ name: string; re: RegExp }> = [\n // Runpod API keys.\n { name: 'runpod_key', re: /\\brpa_[A-Za-z0-9]{16,}\\b/g },\n // Bearer credentials pasted with their header.\n { name: 'bearer', re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/g },\n // Common vendor key prefixes.\n {\n name: 'vendor_key',\n re: /\\b(?:sk|pk|ghp|gho|phc|phx|xoxb|xoxp)[-_][A-Za-z0-9_-]{16,}\\b/g,\n },\n // Hugging Face access tokens.\n { name: 'hugging_face', re: /\\bhf_[A-Za-z0-9]{30,}\\b/g },\n // AWS access key ids.\n { name: 'aws_key', re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n // Three-segment JWTs.\n {\n name: 'jwt',\n re: /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g,\n },\n];\n\nexport function redactAnchoredCredentials(text: string): ScrubResult {\n let redactions = 0;\n let out = text;\n for (const { name, re } of CREDENTIAL_PATTERNS) {\n out = out.replace(re, () => {\n redactions++;\n return `[redacted:${name}]`;\n });\n }\n return { text: out, redactions };\n}\n\n// ---- Stage B: sensitive headers, whole value ----------------------------\n\n// Headers are matched wherever they appear, not only at line start: pastes\n// arrive inline (\"… got 401 with Authorization: Basic xyz | cookie: a=1; b=2\")\n// and the first version, anchored ^…$, missed exactly that and fell through\n// to the token matcher — which redacted the word \"Basic\" and kept the\n// credential. So the VALUE is bounded by its own shape rather than by the end\n// of the line: an auth header is `<scheme> <token>` or a bare token; a cookie\n// header is a `k=v; k=v` chain. Header names are case-insensitive.\nconst AUTH_HEADER =\n /\\b((?:proxy-)?authorization|x-api-key|x-auth-token|x-runpod-token)(\\s*:\\s*)((?:basic|bearer|token|digest|negotiate|ntlm|apikey)\\s+[^\\s|,]+|[^\\s|,]+)/gi;\nconst COOKIE_HEADER =\n /\\b(cookie|set-cookie)(\\s*:\\s*)([^\\s;=,|]+=[^\\s;,|]*(?:\\s*;\\s*[^\\s;=,|]+(?:=[^\\s;,|]*)?)*)/gi;\n\nexport function redactHeaderValues(text: string): ScrubResult {\n let redactions = 0;\n const replace = (match: string, name: string, sep: string, value: string) => {\n // Stage A may already have replaced the value; keep its marker.\n if (MARKER.test(value)) return match;\n redactions++;\n return `${name}${sep}[redacted:header]`;\n };\n const out = text\n .replace(AUTH_HEADER, replace)\n .replace(COOKIE_HEADER, replace);\n return { text: out, redactions };\n}\n\n// ---- Stage C: URL query credentials -----------------------------------\n\n// URL query credentials stop at the next query field, fragment or text boundary.\n// Preserve parameter names and non-secret query fields for diagnostics.\nexport function redactQuerySecrets(text: string): ScrubResult {\n let redactions = 0;\n const out = text.replace(\n /([?&](?:api[_-]?key|access[_-]?token|token|key|secret)=)([^&#\\s\"'<>`]+)/gi,\n (match, prefix: string, value: string) => {\n if (MARKER.test(value)) return match;\n redactions++;\n return `${prefix}[redacted:query]`;\n }\n );\n return { text: out, redactions };\n}\n\n// ---- Stage D: config assignments, by key name ---------------------------\n\n// Keys are compared segment by segment, never by substring: a substring test\n// redacts `tokenizer: llama-3` because it contains \"token\". Split on\n// separators AND camelCase, so `databasePassword` and `clientSecret` become\n// segment lists rather than one opaque word.\nconst SENSITIVE_SEGMENTS = new Set([\n 'password',\n 'passwd',\n 'secret',\n 'token',\n 'authorization',\n 'cookie',\n 'credential',\n 'credentials',\n]);\n\n// `key` alone is too common to be sensitive (primary_key, cache_key). It\n// counts when the segment right before it qualifies it — anywhere in the\n// name, so SERVICE_API_KEY and STRIPE_ACCESS_KEY match, not only api_key.\nconst KEY_QUALIFIERS = new Set([\n 'api',\n 'access',\n 'private',\n 'signing',\n 'secret',\n 'client',\n]);\n\nexport function splitKey(key: string): string[] {\n return key\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .split(/[_\\-.]+/)\n .filter(Boolean);\n}\n\nexport function isSensitiveKey(key: string): boolean {\n const segments = splitKey(key);\n if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment))) return true;\n return segments.some(\n (segment, i) =>\n (segment === 'key' && i > 0 && KEY_QUALIFIERS.has(segments[i - 1])) ||\n // No separator at all: `apikey`, `accesskey`.\n (segment.endsWith('key') && KEY_QUALIFIERS.has(segment.slice(0, -3)))\n );\n}\n\n// Covers JSON, YAML and shell env assignments, including quoted values with\n// spaces or escaped quotes. The field name is kept for diagnostic context.\n//\n// This was one regex with a lookbehind, two quoted-string alternatives, a\n// negated value class and a trailing lookahead. It was unreadable, and every\n// fix to one case broke another: excluding `&` from the value class so a URL's\n// second query parameter survived also truncated `password: p&ss#word123` at\n// the `&` and leaked the rest. Both behaviors were correct for their own\n// input and the character class had no way to tell the two apart, because the\n// distinction is context, not characters.\n//\n// So the scan is written out: a cursor, one small function per token, and the\n// value's terminator set chosen from where the key sits. Each rule below is a\n// named predicate that can be read and tested on its own.\n\nconst QUOTES = new Set(['\"', \"'\"]);\n\n// A value always ends at one of these: `{`/`}` and `,`/`;` close a JSON or\n// inline-YAML member, and a quote cannot appear inside an unquoted value.\nconst VALUE_TERMINATORS = new Set([',', ';', '{', '}', '\"', \"'\"]);\n\n// `&` and `#` end a value ONLY inside a URL query, where they begin the next\n// parameter and the fragment. In a config file they are ordinary password\n// characters — `p&ss#word` is one value, not three.\nconst QUERY_TERMINATORS = new Set(['&', '#']);\n\nfunction isAsciiLetter(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');\n}\n\nfunction isAsciiDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9';\n}\n\n/** A key begins with a letter or underscore, never a digit — so `10:30` in a\n * timestamp is not read as an assignment. */\nfunction isKeyStart(ch: string): boolean {\n return isAsciiLetter(ch) || ch === '_';\n}\n\n/** Inside a key name: adds digits and the three separators that `splitKey`\n * later splits on, so `aws.secretKey` and `DB_PASSWORD` arrive whole. */\nfunction isKeyBody(ch: string): boolean {\n return (\n isAsciiLetter(ch) ||\n isAsciiDigit(ch) ||\n ch === '_' ||\n ch === '.' ||\n ch === '-'\n );\n}\n\n/** Horizontal space only. A newline after the separator means the value is on\n * another line, so `db:\\n password: x` must not read `password` as db's\n * value — the bug that let nested YAML through entirely. */\nfunction isSpaceOrTab(ch: string): boolean {\n return ch === ' ' || ch === '\\t';\n}\n\nfunction isWhitespace(ch: string): boolean {\n return (\n ch === ' ' ||\n ch === '\\t' ||\n ch === '\\n' ||\n ch === '\\r' ||\n ch === '\\f' ||\n ch === '\\v'\n );\n}\n\ninterface Assignment {\n /** Bare key name: quotes and the separator stripped. */\n key: string;\n /** Raw value text as it appears, including its quotes when quoted. */\n raw: string;\n /** Quote character to reinstate around a replacement, or `''`. */\n quote: string;\n /** Half-open range of the value within the source text. */\n start: number;\n end: number;\n}\n\n/** Reads a quoted value, honoring backslash escapes. An unterminated quote is\n * a truncated paste, so it reads to the end of the line rather than giving\n * up: for a redactor, over-reading a secret is the safe direction. */\nfunction readQuotedValue(text: string, start: number): number {\n const quote = text[start];\n let i = start + 1;\n while (i < text.length) {\n const ch = text[i];\n if (ch === '\\\\') {\n i += 2;\n continue;\n }\n if (ch === quote) return i + 1;\n if (ch === '\\n' || ch === '\\r') return i;\n i++;\n }\n return text.length;\n}\n\n/** Reads an unquoted value up to the first terminator for its context. */\nfunction readUnquotedValue(\n text: string,\n start: number,\n inQueryString: boolean\n): number {\n let i = start;\n while (i < text.length) {\n const ch = text[i];\n if (isWhitespace(ch) || VALUE_TERMINATORS.has(ch)) break;\n if (inQueryString && QUERY_TERMINATORS.has(ch)) break;\n i++;\n }\n return i;\n}\n\n/** Parses `key: value` or `key = value` beginning at `at`, or null if there is\n * no assignment there. */\nfunction parseAssignment(text: string, at: number): Assignment | null {\n const before = at > 0 ? text[at - 1] : '';\n // Mid-identifier: `my_password` must not also match as key `password`.\n if (isKeyBody(before)) return null;\n\n let i = at;\n const openingQuote = QUOTES.has(text[i] ?? '') ? text[i] : '';\n if (openingQuote) i++;\n if (!isKeyStart(text[i] ?? '')) return null;\n\n const keyStart = i;\n while (i < text.length && isKeyBody(text[i])) i++;\n const key = text.slice(keyStart, i);\n\n // A closing quote is tolerated whether or not one opened: pasted snippets\n // arrive truncated, and `password\": x` should still redact.\n if (QUOTES.has(text[i] ?? '')) i++;\n\n while (i < text.length && isSpaceOrTab(text[i])) i++;\n if (text[i] !== ':' && text[i] !== '=') return null;\n i++;\n while (i < text.length && isSpaceOrTab(text[i])) i++;\n\n // `?api_key=` or `&token=` — this key sits in a URL query string.\n const inQueryString = before === '?' || before === '&';\n\n const start = i;\n let end: number;\n let quote = '';\n if (QUOTES.has(text[start] ?? '')) {\n quote = text[start];\n end = readQuotedValue(text, start);\n } else {\n end = readUnquotedValue(text, start, inQueryString);\n }\n // A parent key such as `db:` has no value of its own.\n if (end === start) return null;\n\n return { key, raw: text.slice(start, end), quote, start, end };\n}\n\nexport function redactAssignments(text: string): ScrubResult {\n let redactions = 0;\n let out = '';\n let copiedTo = 0;\n let cursor = 0;\n\n while (cursor < text.length) {\n const found = parseAssignment(text, cursor);\n if (!found) {\n cursor++;\n continue;\n }\n\n // Already handled by an earlier stage — keep its marker, do not count it\n // twice, and resume past it.\n if (MARKER.test(found.raw)) {\n cursor = found.end;\n continue;\n }\n\n if (!isSensitiveKey(found.key)) {\n // Resume INSIDE the value, not after it. A non-sensitive assignment can\n // contain one: `https://h/?password=x` parses as key `https` whose value\n // is the whole URL, and skipping it leaked the password outright.\n cursor = found.start;\n continue;\n }\n\n out += text.slice(copiedTo, found.start);\n out += `${found.quote}[redacted:config]${found.quote}`;\n copiedTo = found.end;\n cursor = found.end;\n redactions++;\n }\n\n out += text.slice(copiedTo);\n return { text: out, redactions };\n}\n\n// ---- Stage E: email addresses -------------------------------------------\n\n// Letters, marks and digits of any script, so `josé@example.com` and\n// `用户@例子.example.com` are caught too. Quantifiers carry the address limits\n// (64-character local part, 63-character labels), and the local part must\n// start a run of address characters, so each run is tried once and a run of\n// more than 64 characters before the `@` is not an address. Together they\n// keep a long run with no `@` from costing quadratic time on a long\n// submission. A dotted version such as `pkg@1.2.3` is not matched: the\n// top-level label must be letters.\nconst EMAIL =\n /(?<![\\p{L}\\p{M}\\p{N}_.%+-])([\\p{L}\\p{M}\\p{N}_.%+-]{1,64})@((?:[\\p{L}\\p{M}\\p{N}-]{1,63}\\.){1,8}[\\p{L}\\p{M}]{2,63})(?![\\p{L}\\p{M}\\p{N}])/gu;\n\n// Two logins are kept: they name a machine, not a person, and the address is\n// what tells a reader where a command ran. They are Runpod's Pod SSH proxy\n// (`<podId>-<hash>@ssh.runpod.io`) and the git user of an SSH remote\n// (`git@github.com:org/repo.git`, `ssh://git@github.com/org/repo.git`).\nconst RUNPOD_SSH_HOST = 'ssh.runpod.io';\n\nexport function redactEmails(text: string): ScrubResult {\n let redactions = 0;\n const out = text.replace(\n EMAIL,\n (address: string, local: string, domain: string, offset: number) => {\n const next = text[offset + address.length];\n if (\n domain.toLowerCase() === RUNPOD_SSH_HOST ||\n (local === 'git' && (next === ':' || next === '/'))\n )\n return address;\n redactions++;\n return '[redacted:email]';\n }\n );\n return { text: out, redactions };\n}\n\n// ---- Composition ---------------------------------------------------------\n\nexport function scrub(text: string): ScrubResult {\n let out = text;\n let redactions = 0;\n for (const stage of [\n redactAnchoredCredentials,\n redactHeaderValues,\n redactQuerySecrets,\n redactAssignments,\n redactEmails,\n ]) {\n const result = stage(out);\n out = result.text;\n redactions += result.redactions;\n }\n return { text: out, redactions };\n}\n\n// Only user-authored text is scrubbed; resolved identity and server timestamps\n// remain authoritative. Reapplying this at the sink is safe and idempotent.\nexport const SCRUBBED_FIELDS = [\n 'content',\n 'intention',\n 'modelType',\n 'severity',\n 'tool',\n 'workaround',\n 'trigger',\n 'source',\n 'useCase',\n 'harness',\n 'harnessSource',\n 'transport',\n] as const;\n\nexport function scrubSubmission<\n T extends {\n content: string;\n intention?: string;\n modelType?: string;\n severity?: string;\n tool?: string;\n workaround?: string;\n trigger?: string;\n source?: string;\n useCase?: string;\n harness?: string;\n harnessSource?: string;\n transport?: string;\n redactions: number;\n scrubVersion: number;\n },\n>(row: T): T {\n const clean = { ...row };\n // EVERY agent-writable string field belongs in this list. A field added to\n // the submission shape but not here is stored unscrubbed, which is how a\n // pasted credential reaches the table — the fields most likely to carry one\n // are exactly the free-text ones a new route adds. Guarded by a test that\n // walks the submission type, so the omission fails CI rather than shipping.\n for (const field of SCRUBBED_FIELDS) {\n const value = clean[field];\n if (typeof value !== 'string') continue;\n const result = scrub(value);\n clean[field] = result.text;\n clean.redactions += result.redactions;\n }\n clean.scrubVersion = SCRUB_VERSION;\n return clean;\n}\n"],"mappings":"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","names":[]}