@runpod/mcp-server 4.7.0 → 4.9.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.
- package/CHANGELOG.md +18 -0
- package/README.md +9 -0
- package/dist/alp.cjs +319 -0
- package/dist/alp.cjs.map +1 -0
- package/dist/alp.d.cts +20 -0
- package/dist/alp.d.ts +20 -0
- package/dist/alp.mjs +25 -0
- package/dist/alp.mjs.map +1 -0
- package/dist/chunk-577IYCJT.mjs +53 -0
- package/dist/chunk-577IYCJT.mjs.map +1 -0
- package/dist/chunk-6IETGSON.mjs +135 -0
- package/dist/chunk-6IETGSON.mjs.map +1 -0
- package/dist/chunk-H3NZYQIB.mjs +236 -0
- package/dist/chunk-H3NZYQIB.mjs.map +1 -0
- package/dist/chunk-VKLWTK4O.mjs +10671 -0
- package/dist/chunk-VKLWTK4O.mjs.map +1 -0
- package/dist/hosted.cjs +11272 -0
- package/dist/hosted.cjs.map +1 -0
- package/dist/hosted.d.cts +58 -0
- package/dist/hosted.d.ts +58 -0
- package/dist/hosted.mjs +241 -0
- package/dist/hosted.mjs.map +1 -0
- package/dist/http-BaACCis6.d.cts +75 -0
- package/dist/http-BcFDW7LG.d.ts +75 -0
- package/dist/http.cjs +115 -8
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +5 -78
- package/dist/http.d.ts +5 -78
- package/dist/http.mjs +13 -10602
- package/dist/http.mjs.map +1 -1
- package/dist/sink-Cm8lp16D.d.cts +17 -0
- package/dist/sink-Cm8lp16D.d.ts +17 -0
- package/dist/specgen/profile.cjs +163 -0
- package/dist/specgen/profile.cjs.map +1 -0
- package/dist/specgen/profile.d.cts +22 -0
- package/dist/specgen/profile.d.ts +22 -0
- package/dist/specgen/profile.mjs +15 -0
- package/dist/specgen/profile.mjs.map +1 -0
- package/dist/stdio.cjs +115 -8
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +115 -8
- package/dist/stdio.mjs.map +1 -1
- package/package.json +17 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/specgen/profile.ts
|
|
2
|
+
var DIRECTORY_PATH = "/directory";
|
|
3
|
+
var IN = { include: true };
|
|
4
|
+
var CREDENTIAL_MANAGEMENT = "Manages stored credentials; OpenAI restricts plugins that handle access credentials, so the directory leaves the whole credential family out.";
|
|
5
|
+
var CREDENTIAL_INPUT = "Takes a secret or password as input; directories forbid collecting credentials.";
|
|
6
|
+
var ALP = "Collects conversation-derived data beyond the task; the directory profile never serves it.";
|
|
7
|
+
var DIRECTORY_TOOLS = {
|
|
8
|
+
"cancel-job": IN,
|
|
9
|
+
"create-cluster": IN,
|
|
10
|
+
"create-delegation": IN,
|
|
11
|
+
"create-endpoint": IN,
|
|
12
|
+
"create-global-volume": IN,
|
|
13
|
+
"create-network-volume": IN,
|
|
14
|
+
"create-pod": IN,
|
|
15
|
+
"create-template": IN,
|
|
16
|
+
"delete-cluster": IN,
|
|
17
|
+
"delete-endpoint": IN,
|
|
18
|
+
"delete-global-volume": IN,
|
|
19
|
+
"delete-network-volume": IN,
|
|
20
|
+
"delete-pod": IN,
|
|
21
|
+
"delete-template": IN,
|
|
22
|
+
"deploy-hub-repo": IN,
|
|
23
|
+
"endpoint-health": IN,
|
|
24
|
+
"get-capacity": IN,
|
|
25
|
+
"get-cluster": IN,
|
|
26
|
+
"get-cpu-type": IN,
|
|
27
|
+
"get-data-center": IN,
|
|
28
|
+
"get-endpoint": IN,
|
|
29
|
+
"get-endpoint-build": IN,
|
|
30
|
+
"get-global-volume": IN,
|
|
31
|
+
"get-gpu-type": IN,
|
|
32
|
+
"get-job-status": IN,
|
|
33
|
+
"get-network-volume": IN,
|
|
34
|
+
"get-organization": IN,
|
|
35
|
+
"get-organization-user": IN,
|
|
36
|
+
"get-pod": IN,
|
|
37
|
+
"get-ssh-keys": IN,
|
|
38
|
+
"get-template": IN,
|
|
39
|
+
"list-audit-logs": IN,
|
|
40
|
+
"list-billing": IN,
|
|
41
|
+
"list-cluster-billing": IN,
|
|
42
|
+
"list-cluster-pods": IN,
|
|
43
|
+
"list-clusters": IN,
|
|
44
|
+
"list-cpu-types": IN,
|
|
45
|
+
"list-data-centers": IN,
|
|
46
|
+
"list-delegations": IN,
|
|
47
|
+
"list-endpoint-billing": IN,
|
|
48
|
+
"list-endpoint-builds": IN,
|
|
49
|
+
"list-endpoint-releases": IN,
|
|
50
|
+
"list-endpoint-workers": IN,
|
|
51
|
+
"list-endpoints": IN,
|
|
52
|
+
"list-global-volumes": IN,
|
|
53
|
+
"list-gpu-types": IN,
|
|
54
|
+
"list-hub-repos": IN,
|
|
55
|
+
"list-network-volume-billing": IN,
|
|
56
|
+
"list-network-volumes": IN,
|
|
57
|
+
"list-organization-users": IN,
|
|
58
|
+
"list-pod-billing": IN,
|
|
59
|
+
"list-pods": IN,
|
|
60
|
+
"list-public-endpoints": IN,
|
|
61
|
+
"list-public-templates": IN,
|
|
62
|
+
"list-serverless-billing": IN,
|
|
63
|
+
"list-templates": IN,
|
|
64
|
+
"lookup-concept": IN,
|
|
65
|
+
"purge-endpoint-queue": IN,
|
|
66
|
+
"retry-job": IN,
|
|
67
|
+
"revoke-delegation": IN,
|
|
68
|
+
"search-concepts": IN,
|
|
69
|
+
"search-docs": IN,
|
|
70
|
+
"set-endpoint-gpus": IN,
|
|
71
|
+
"stream-job": IN,
|
|
72
|
+
"stream-pod-logs": IN,
|
|
73
|
+
"stream-worker-logs": IN,
|
|
74
|
+
"update-cluster": IN,
|
|
75
|
+
"update-endpoint": IN,
|
|
76
|
+
"update-global-volume": IN,
|
|
77
|
+
"update-network-volume": IN,
|
|
78
|
+
"update-pod": IN,
|
|
79
|
+
"update-ssh-keys": IN,
|
|
80
|
+
"update-template": IN,
|
|
81
|
+
"run-endpoint": { include: true, omitInputs: ["s3Config"] },
|
|
82
|
+
"runsync-endpoint": { include: true, omitInputs: ["s3Config"] },
|
|
83
|
+
"pod-action": {
|
|
84
|
+
include: false,
|
|
85
|
+
reason: "Takes the transition as an argument; OpenAI asks for one tool per operation, so the directory serves start-pod, stop-pod and restart-pod (terminate is delete-pod)."
|
|
86
|
+
},
|
|
87
|
+
"start-pod": IN,
|
|
88
|
+
"stop-pod": IN,
|
|
89
|
+
"restart-pod": IN,
|
|
90
|
+
"list-guides": {
|
|
91
|
+
include: false,
|
|
92
|
+
reason: "Serves playbooks that instruct the model how to behave."
|
|
93
|
+
},
|
|
94
|
+
"read-guide": {
|
|
95
|
+
include: false,
|
|
96
|
+
reason: "Serves playbooks that instruct the model how to behave."
|
|
97
|
+
},
|
|
98
|
+
"describe-tool": {
|
|
99
|
+
include: false,
|
|
100
|
+
reason: "Fetches schemas the directory already lists in full; directories reject schema fetching."
|
|
101
|
+
},
|
|
102
|
+
"create-secret": { include: false, reason: CREDENTIAL_INPUT },
|
|
103
|
+
"get-secret": { include: false, reason: CREDENTIAL_MANAGEMENT },
|
|
104
|
+
"list-secrets": { include: false, reason: CREDENTIAL_MANAGEMENT },
|
|
105
|
+
"delete-secret": { include: false, reason: CREDENTIAL_MANAGEMENT },
|
|
106
|
+
"get-registry": { include: false, reason: CREDENTIAL_MANAGEMENT },
|
|
107
|
+
"list-registries": { include: false, reason: CREDENTIAL_MANAGEMENT },
|
|
108
|
+
"delete-registry": { include: false, reason: CREDENTIAL_MANAGEMENT },
|
|
109
|
+
"update-secret": { include: false, reason: CREDENTIAL_INPUT },
|
|
110
|
+
"create-registry": { include: false, reason: CREDENTIAL_INPUT },
|
|
111
|
+
report_feedback: { include: false, reason: ALP },
|
|
112
|
+
report_how_user_found_runpod: { include: false, reason: ALP },
|
|
113
|
+
save_to_journal: { include: false, reason: ALP },
|
|
114
|
+
read_journal: { include: false, reason: ALP },
|
|
115
|
+
ask_question: { include: false, reason: ALP }
|
|
116
|
+
};
|
|
117
|
+
function directoryIncludes(name) {
|
|
118
|
+
return DIRECTORY_TOOLS[name]?.include === true;
|
|
119
|
+
}
|
|
120
|
+
function directoryOmittedInputs(name) {
|
|
121
|
+
const decision = DIRECTORY_TOOLS[name];
|
|
122
|
+
return decision?.include ? decision.omitInputs ?? [] : [];
|
|
123
|
+
}
|
|
124
|
+
function profileForPath(pathname) {
|
|
125
|
+
return pathname.replace(/\/+$/, "") === DIRECTORY_PATH ? "directory" : "default";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
DIRECTORY_PATH,
|
|
130
|
+
DIRECTORY_TOOLS,
|
|
131
|
+
directoryIncludes,
|
|
132
|
+
directoryOmittedInputs,
|
|
133
|
+
profileForPath
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=chunk-6IETGSON.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/specgen/profile.ts"],"sourcesContent":["// Which tool surface a connection gets. `default` is the full surface every\n// client has always had. `directory` is what app directories (Anthropic's\n// connector directory, OpenAI's plugin directory) list: their policies forbid\n// tool descriptions that instruct the model, steer it toward other tools or\n// external instructions, or collect conversation data beyond the tool's job.\n// The directory profile is served only at DIRECTORY_PATH; nothing about the\n// default surface changes.\nexport type ServerProfile = 'default' | 'directory';\n\nexport const DIRECTORY_PATH = '/directory';\n\n/** Whether a tool is listed on the directory profile. Every tool the server\n * can serve must have an entry, so a new tool (a spec regeneration, a new\n * curated tool) fails tests/specgen-directory.test.ts until someone decides.\n * The rules for deciding are in specgen/DIRECTORY_POLICY.md.\n * `omitInputs` hides top-level arguments the directory must not ask for (a\n * credential, for example); a call that passes one is refused. */\nexport type DirectoryDecision =\n | { include: true; omitInputs?: readonly string[] }\n | { include: false; reason: string };\n\nconst IN = { include: true } as const;\nconst CREDENTIAL_MANAGEMENT =\n 'Manages stored credentials; OpenAI restricts plugins that handle access credentials, so the directory leaves the whole credential family out.';\nconst CREDENTIAL_INPUT =\n 'Takes a secret or password as input; directories forbid collecting credentials.';\nconst ALP =\n 'Collects conversation-derived data beyond the task; the directory profile never serves it.';\n\nexport const DIRECTORY_TOOLS: Readonly<Record<string, DirectoryDecision>> = {\n 'cancel-job': IN,\n 'create-cluster': IN,\n 'create-delegation': IN,\n 'create-endpoint': IN,\n 'create-global-volume': IN,\n 'create-network-volume': IN,\n 'create-pod': IN,\n 'create-template': IN,\n 'delete-cluster': IN,\n 'delete-endpoint': IN,\n 'delete-global-volume': IN,\n 'delete-network-volume': IN,\n 'delete-pod': IN,\n 'delete-template': IN,\n 'deploy-hub-repo': IN,\n 'endpoint-health': IN,\n 'get-capacity': IN,\n 'get-cluster': IN,\n 'get-cpu-type': IN,\n 'get-data-center': IN,\n 'get-endpoint': IN,\n 'get-endpoint-build': IN,\n 'get-global-volume': IN,\n 'get-gpu-type': IN,\n 'get-job-status': IN,\n 'get-network-volume': IN,\n 'get-organization': IN,\n 'get-organization-user': IN,\n 'get-pod': IN,\n 'get-ssh-keys': IN,\n 'get-template': IN,\n 'list-audit-logs': IN,\n 'list-billing': IN,\n 'list-cluster-billing': IN,\n 'list-cluster-pods': IN,\n 'list-clusters': IN,\n 'list-cpu-types': IN,\n 'list-data-centers': IN,\n 'list-delegations': IN,\n 'list-endpoint-billing': IN,\n 'list-endpoint-builds': IN,\n 'list-endpoint-releases': IN,\n 'list-endpoint-workers': IN,\n 'list-endpoints': IN,\n 'list-global-volumes': IN,\n 'list-gpu-types': IN,\n 'list-hub-repos': IN,\n 'list-network-volume-billing': IN,\n 'list-network-volumes': IN,\n 'list-organization-users': IN,\n 'list-pod-billing': IN,\n 'list-pods': IN,\n 'list-public-endpoints': IN,\n 'list-public-templates': IN,\n 'list-serverless-billing': IN,\n 'list-templates': IN,\n 'lookup-concept': IN,\n 'purge-endpoint-queue': IN,\n 'retry-job': IN,\n 'revoke-delegation': IN,\n 'search-concepts': IN,\n 'search-docs': IN,\n 'set-endpoint-gpus': IN,\n 'stream-job': IN,\n 'stream-pod-logs': IN,\n 'stream-worker-logs': IN,\n 'update-cluster': IN,\n 'update-endpoint': IN,\n 'update-global-volume': IN,\n 'update-network-volume': IN,\n 'update-pod': IN,\n 'update-ssh-keys': IN,\n 'update-template': IN,\n 'run-endpoint': { include: true, omitInputs: ['s3Config'] },\n 'runsync-endpoint': { include: true, omitInputs: ['s3Config'] },\n 'pod-action': {\n include: false,\n reason:\n 'Takes the transition as an argument; OpenAI asks for one tool per operation, so the directory serves start-pod, stop-pod and restart-pod (terminate is delete-pod).',\n },\n 'start-pod': IN,\n 'stop-pod': IN,\n 'restart-pod': IN,\n 'list-guides': {\n include: false,\n reason: 'Serves playbooks that instruct the model how to behave.',\n },\n 'read-guide': {\n include: false,\n reason: 'Serves playbooks that instruct the model how to behave.',\n },\n 'describe-tool': {\n include: false,\n reason:\n 'Fetches schemas the directory already lists in full; directories reject schema fetching.',\n },\n 'create-secret': { include: false, reason: CREDENTIAL_INPUT },\n 'get-secret': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'list-secrets': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'delete-secret': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'get-registry': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'list-registries': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'delete-registry': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'update-secret': { include: false, reason: CREDENTIAL_INPUT },\n 'create-registry': { include: false, reason: CREDENTIAL_INPUT },\n report_feedback: { include: false, reason: ALP },\n report_how_user_found_runpod: { include: false, reason: ALP },\n save_to_journal: { include: false, reason: ALP },\n read_journal: { include: false, reason: ALP },\n ask_question: { include: false, reason: ALP },\n};\n\n/** Served on the directory profile only with an explicit include. */\nexport function directoryIncludes(name: string): boolean {\n return DIRECTORY_TOOLS[name]?.include === true;\n}\n\nexport function directoryOmittedInputs(name: string): readonly string[] {\n const decision = DIRECTORY_TOOLS[name];\n return decision?.include ? (decision.omitInputs ?? []) : [];\n}\n\nexport function profileForPath(pathname: string): ServerProfile {\n return pathname.replace(/\\/+$/, '') === DIRECTORY_PATH\n ? 'directory'\n : 'default';\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// src/alp/scrub.ts
|
|
2
|
+
var SCRUB_VERSION = 5;
|
|
3
|
+
var MARKER = /^["']?\[redacted:[a-z_]+\]["']?$/;
|
|
4
|
+
var CREDENTIAL_PATTERNS = [
|
|
5
|
+
// Runpod API keys.
|
|
6
|
+
{ name: "runpod_key", re: /\brpa_[A-Za-z0-9]{16,}\b/g },
|
|
7
|
+
// Bearer credentials pasted with their header.
|
|
8
|
+
{ name: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/g },
|
|
9
|
+
// Common vendor key prefixes.
|
|
10
|
+
{
|
|
11
|
+
name: "vendor_key",
|
|
12
|
+
re: /\b(?:sk|pk|ghp|gho|phc|phx|xoxb|xoxp)[-_][A-Za-z0-9_-]{16,}\b/g
|
|
13
|
+
},
|
|
14
|
+
// Hugging Face access tokens.
|
|
15
|
+
{ name: "hugging_face", re: /\bhf_[A-Za-z0-9]{30,}\b/g },
|
|
16
|
+
// AWS access key ids.
|
|
17
|
+
{ name: "aws_key", re: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
18
|
+
// Three-segment JWTs.
|
|
19
|
+
{
|
|
20
|
+
name: "jwt",
|
|
21
|
+
re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
function redactAnchoredCredentials(text) {
|
|
25
|
+
let redactions = 0;
|
|
26
|
+
let out = text;
|
|
27
|
+
for (const { name, re } of CREDENTIAL_PATTERNS) {
|
|
28
|
+
out = out.replace(re, () => {
|
|
29
|
+
redactions++;
|
|
30
|
+
return `[redacted:${name}]`;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return { text: out, redactions };
|
|
34
|
+
}
|
|
35
|
+
var AUTH_HEADER = /\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;
|
|
36
|
+
var COOKIE_HEADER = /\b(cookie|set-cookie)(\s*:\s*)([^\s;=,|]+=[^\s;,|]*(?:\s*;\s*[^\s;=,|]+(?:=[^\s;,|]*)?)*)/gi;
|
|
37
|
+
function redactHeaderValues(text) {
|
|
38
|
+
let redactions = 0;
|
|
39
|
+
const replace = (match, name, sep, value) => {
|
|
40
|
+
if (MARKER.test(value)) return match;
|
|
41
|
+
redactions++;
|
|
42
|
+
return `${name}${sep}[redacted:header]`;
|
|
43
|
+
};
|
|
44
|
+
const out = text.replace(AUTH_HEADER, replace).replace(COOKIE_HEADER, replace);
|
|
45
|
+
return { text: out, redactions };
|
|
46
|
+
}
|
|
47
|
+
function redactQuerySecrets(text) {
|
|
48
|
+
let redactions = 0;
|
|
49
|
+
const out = text.replace(
|
|
50
|
+
/([?&](?:api[_-]?key|access[_-]?token|token|key|secret)=)([^&#\s"'<>`]+)/gi,
|
|
51
|
+
(match, prefix, value) => {
|
|
52
|
+
if (MARKER.test(value)) return match;
|
|
53
|
+
redactions++;
|
|
54
|
+
return `${prefix}[redacted:query]`;
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
return { text: out, redactions };
|
|
58
|
+
}
|
|
59
|
+
var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
60
|
+
"password",
|
|
61
|
+
"passwd",
|
|
62
|
+
"secret",
|
|
63
|
+
"token",
|
|
64
|
+
"authorization",
|
|
65
|
+
"cookie",
|
|
66
|
+
"credential",
|
|
67
|
+
"credentials"
|
|
68
|
+
]);
|
|
69
|
+
var KEY_QUALIFIERS = /* @__PURE__ */ new Set([
|
|
70
|
+
"api",
|
|
71
|
+
"access",
|
|
72
|
+
"private",
|
|
73
|
+
"signing",
|
|
74
|
+
"secret",
|
|
75
|
+
"client"
|
|
76
|
+
]);
|
|
77
|
+
function splitKey(key) {
|
|
78
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-.]+/).filter(Boolean);
|
|
79
|
+
}
|
|
80
|
+
function isSensitiveKey(key) {
|
|
81
|
+
const segments = splitKey(key);
|
|
82
|
+
if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment))) return true;
|
|
83
|
+
return segments.some(
|
|
84
|
+
(segment, i) => segment === "key" && i > 0 && KEY_QUALIFIERS.has(segments[i - 1]) || // No separator at all: `apikey`, `accesskey`.
|
|
85
|
+
segment.endsWith("key") && KEY_QUALIFIERS.has(segment.slice(0, -3))
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
var QUOTES = /* @__PURE__ */ new Set(['"', "'"]);
|
|
89
|
+
var VALUE_TERMINATORS = /* @__PURE__ */ new Set([",", ";", "{", "}", '"', "'"]);
|
|
90
|
+
var QUERY_TERMINATORS = /* @__PURE__ */ new Set(["&", "#"]);
|
|
91
|
+
function isAsciiLetter(ch) {
|
|
92
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z";
|
|
93
|
+
}
|
|
94
|
+
function isAsciiDigit(ch) {
|
|
95
|
+
return ch >= "0" && ch <= "9";
|
|
96
|
+
}
|
|
97
|
+
function isKeyStart(ch) {
|
|
98
|
+
return isAsciiLetter(ch) || ch === "_";
|
|
99
|
+
}
|
|
100
|
+
function isKeyBody(ch) {
|
|
101
|
+
return isAsciiLetter(ch) || isAsciiDigit(ch) || ch === "_" || ch === "." || ch === "-";
|
|
102
|
+
}
|
|
103
|
+
function isSpaceOrTab(ch) {
|
|
104
|
+
return ch === " " || ch === " ";
|
|
105
|
+
}
|
|
106
|
+
function isWhitespace(ch) {
|
|
107
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
108
|
+
}
|
|
109
|
+
function readQuotedValue(text, start) {
|
|
110
|
+
const quote = text[start];
|
|
111
|
+
let i = start + 1;
|
|
112
|
+
while (i < text.length) {
|
|
113
|
+
const ch = text[i];
|
|
114
|
+
if (ch === "\\") {
|
|
115
|
+
i += 2;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ch === quote) return i + 1;
|
|
119
|
+
if (ch === "\n" || ch === "\r") return i;
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
return text.length;
|
|
123
|
+
}
|
|
124
|
+
function readUnquotedValue(text, start, inQueryString) {
|
|
125
|
+
let i = start;
|
|
126
|
+
while (i < text.length) {
|
|
127
|
+
const ch = text[i];
|
|
128
|
+
if (isWhitespace(ch) || VALUE_TERMINATORS.has(ch)) break;
|
|
129
|
+
if (inQueryString && QUERY_TERMINATORS.has(ch)) break;
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
return i;
|
|
133
|
+
}
|
|
134
|
+
function parseAssignment(text, at) {
|
|
135
|
+
const before = at > 0 ? text[at - 1] : "";
|
|
136
|
+
if (isKeyBody(before)) return null;
|
|
137
|
+
let i = at;
|
|
138
|
+
const openingQuote = QUOTES.has(text[i] ?? "") ? text[i] : "";
|
|
139
|
+
if (openingQuote) i++;
|
|
140
|
+
if (!isKeyStart(text[i] ?? "")) return null;
|
|
141
|
+
const keyStart = i;
|
|
142
|
+
while (i < text.length && isKeyBody(text[i])) i++;
|
|
143
|
+
const key = text.slice(keyStart, i);
|
|
144
|
+
if (QUOTES.has(text[i] ?? "")) i++;
|
|
145
|
+
while (i < text.length && isSpaceOrTab(text[i])) i++;
|
|
146
|
+
if (text[i] !== ":" && text[i] !== "=") return null;
|
|
147
|
+
i++;
|
|
148
|
+
while (i < text.length && isSpaceOrTab(text[i])) i++;
|
|
149
|
+
const inQueryString = before === "?" || before === "&";
|
|
150
|
+
const start = i;
|
|
151
|
+
let end;
|
|
152
|
+
let quote = "";
|
|
153
|
+
if (QUOTES.has(text[start] ?? "")) {
|
|
154
|
+
quote = text[start];
|
|
155
|
+
end = readQuotedValue(text, start);
|
|
156
|
+
} else {
|
|
157
|
+
end = readUnquotedValue(text, start, inQueryString);
|
|
158
|
+
}
|
|
159
|
+
if (end === start) return null;
|
|
160
|
+
return { key, raw: text.slice(start, end), quote, start, end };
|
|
161
|
+
}
|
|
162
|
+
function redactAssignments(text) {
|
|
163
|
+
let redactions = 0;
|
|
164
|
+
let out = "";
|
|
165
|
+
let copiedTo = 0;
|
|
166
|
+
let cursor = 0;
|
|
167
|
+
while (cursor < text.length) {
|
|
168
|
+
const found = parseAssignment(text, cursor);
|
|
169
|
+
if (!found) {
|
|
170
|
+
cursor++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (MARKER.test(found.raw)) {
|
|
174
|
+
cursor = found.end;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!isSensitiveKey(found.key)) {
|
|
178
|
+
cursor = found.start;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
out += text.slice(copiedTo, found.start);
|
|
182
|
+
out += `${found.quote}[redacted:config]${found.quote}`;
|
|
183
|
+
copiedTo = found.end;
|
|
184
|
+
cursor = found.end;
|
|
185
|
+
redactions++;
|
|
186
|
+
}
|
|
187
|
+
out += text.slice(copiedTo);
|
|
188
|
+
return { text: out, redactions };
|
|
189
|
+
}
|
|
190
|
+
function scrub(text) {
|
|
191
|
+
let out = text;
|
|
192
|
+
let redactions = 0;
|
|
193
|
+
for (const stage of [
|
|
194
|
+
redactAnchoredCredentials,
|
|
195
|
+
redactHeaderValues,
|
|
196
|
+
redactQuerySecrets,
|
|
197
|
+
redactAssignments
|
|
198
|
+
]) {
|
|
199
|
+
const result = stage(out);
|
|
200
|
+
out = result.text;
|
|
201
|
+
redactions += result.redactions;
|
|
202
|
+
}
|
|
203
|
+
return { text: out, redactions };
|
|
204
|
+
}
|
|
205
|
+
var SCRUBBED_FIELDS = [
|
|
206
|
+
"content",
|
|
207
|
+
"intention",
|
|
208
|
+
"modelType",
|
|
209
|
+
"severity",
|
|
210
|
+
"tool",
|
|
211
|
+
"workaround",
|
|
212
|
+
"trigger",
|
|
213
|
+
"source",
|
|
214
|
+
"useCase",
|
|
215
|
+
"harness",
|
|
216
|
+
"harnessSource",
|
|
217
|
+
"transport"
|
|
218
|
+
];
|
|
219
|
+
function scrubSubmission(row) {
|
|
220
|
+
const clean = { ...row };
|
|
221
|
+
for (const field of SCRUBBED_FIELDS) {
|
|
222
|
+
const value = clean[field];
|
|
223
|
+
if (typeof value !== "string") continue;
|
|
224
|
+
const result = scrub(value);
|
|
225
|
+
clean[field] = result.text;
|
|
226
|
+
clean.redactions += result.redactions;
|
|
227
|
+
}
|
|
228
|
+
clean.scrubVersion = SCRUB_VERSION;
|
|
229
|
+
return clean;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export {
|
|
233
|
+
SCRUB_VERSION,
|
|
234
|
+
scrubSubmission
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=chunk-H3NZYQIB.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 and sensitive config\n// assignments; arbitrary unlabeled secrets cannot reliably be identified.\n//\n// Four 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//\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 = 5;\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// ---- 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 ]) {\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":[]}
|