@tracemarketplace/shared 0.0.1 → 0.0.2
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/redact.d.ts +13 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +114 -0
- package/dist/redact.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/redact.ts +138 -0
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./hash.js";
|
|
|
3
3
|
export * from "./scoring.js";
|
|
4
4
|
export * from "./utils.js";
|
|
5
5
|
export * from "./validators.js";
|
|
6
|
+
export * from "./redact.js";
|
|
6
7
|
export { extractClaudeCode } from "./extractors/claude-code.js";
|
|
7
8
|
export { extractCodex } from "./extractors/codex.js";
|
|
8
9
|
export { extractCursor } from "./extractors/cursor.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./hash.js";
|
|
|
3
3
|
export * from "./scoring.js";
|
|
4
4
|
export * from "./utils.js";
|
|
5
5
|
export * from "./validators.js";
|
|
6
|
+
export * from "./redact.js";
|
|
6
7
|
export { extractClaudeCode } from "./extractors/claude-code.js";
|
|
7
8
|
export { extractCodex } from "./extractors/codex.js";
|
|
8
9
|
export { extractCursor } from "./extractors/cursor.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NormalizedTrace } from "./types.js";
|
|
2
|
+
export interface RedactOptions {
|
|
3
|
+
/** Pass os.homedir() from the CLI — strips absolute home paths from all strings. */
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Redact PII and secrets from a trace before it leaves the user's machine.
|
|
8
|
+
*
|
|
9
|
+
* Pass `homeDir: os.homedir()` from the CLI so absolute paths are stripped.
|
|
10
|
+
* The server runs a second pass (without homeDir) as a safety net.
|
|
11
|
+
*/
|
|
12
|
+
export declare function redactTrace(trace: NormalizedTrace, opts?: RedactOptions): NormalizedTrace;
|
|
13
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAoGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,IAAI,GAAE,aAAkB,GACvB,eAAe,CAuBjB"}
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ─── Secret patterns ────────────────────────────────────────────────────────
|
|
2
|
+
// Ordered from specific → generic to avoid partial matches being swallowed.
|
|
3
|
+
const SECRET_PATTERNS = [
|
|
4
|
+
// Anthropic
|
|
5
|
+
{ re: /sk-ant-[a-zA-Z0-9\-_]{20,}/g, label: "ANTHROPIC_KEY" },
|
|
6
|
+
// OpenAI (must come before generic sk- catch-all)
|
|
7
|
+
{ re: /sk-proj-[a-zA-Z0-9\-_]{20,}/g, label: "OPENAI_KEY" },
|
|
8
|
+
{ re: /sk-[a-zA-Z0-9]{20,}/g, label: "OPENAI_KEY" },
|
|
9
|
+
// AWS
|
|
10
|
+
{ re: /AKIA[0-9A-Z]{16}/g, label: "AWS_ACCESS_KEY" },
|
|
11
|
+
{ re: /(aws_secret_access_key\s*[=:]\s*)[A-Za-z0-9/+]{40}/gi, label: "AWS_SECRET_KEY" },
|
|
12
|
+
// GitHub
|
|
13
|
+
{ re: /github_pat_[a-zA-Z0-9_]{82}/g, label: "GITHUB_PAT" },
|
|
14
|
+
{ re: /ghp_[a-zA-Z0-9]{36}/g, label: "GITHUB_TOKEN" },
|
|
15
|
+
{ re: /ghs_[a-zA-Z0-9]{36}/g, label: "GITHUB_TOKEN" },
|
|
16
|
+
// Stripe
|
|
17
|
+
{ re: /sk_live_[a-zA-Z0-9]{24,}/g, label: "STRIPE_SECRET_KEY" },
|
|
18
|
+
{ re: /rk_live_[a-zA-Z0-9]{24,}/g, label: "STRIPE_RESTRICTED_KEY" },
|
|
19
|
+
// Resend
|
|
20
|
+
{ re: /re_[a-zA-Z0-9]{32,}/g, label: "RESEND_KEY" },
|
|
21
|
+
// JWTs — eyJ<base64>.<base64>.<base64>
|
|
22
|
+
{ re: /eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]*/g, label: "JWT" },
|
|
23
|
+
// Bearer tokens in Authorization headers
|
|
24
|
+
{ re: /(Bearer\s+)[a-zA-Z0-9\-._~+/]+=*/gi, label: "BEARER_TOKEN" },
|
|
25
|
+
// Passwords in URLs: https://user:PASSWORD@host
|
|
26
|
+
{ re: /(https?:\/\/[^:@\s]+:)[^:@\s]+(@)/g, label: "URL_PASSWORD" },
|
|
27
|
+
// Database DSNs: postgres://user:PASSWORD@host
|
|
28
|
+
{ re: /((?:postgres(?:ql)?|mysql|redis):\/\/[^:]+:)[^@\s]+(@)/g, label: "DB_PASSWORD" },
|
|
29
|
+
// Generic key/secret assignments: API_KEY=abc123... or secret: "abc123..."
|
|
30
|
+
{
|
|
31
|
+
re: /((?:api[_-]?key|api[_-]?secret|access[_-]?token|auth[_-]?token|private[_-]?key|client[_-]?secret)\s*[=:]\s*["']?)[a-zA-Z0-9\-_.+/]{16,}(["']?)/gi,
|
|
32
|
+
label: "SECRET_VALUE",
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
// ─── Core string transforms ──────────────────────────────────────────────────
|
|
36
|
+
function stripHome(s, home) {
|
|
37
|
+
return home ? s.replaceAll(home, "~") : s;
|
|
38
|
+
}
|
|
39
|
+
function stripSecrets(s) {
|
|
40
|
+
let out = s;
|
|
41
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
42
|
+
// Patterns with capture groups: preserve group 1 (key name), replace group 2 (value)
|
|
43
|
+
if (re.source.includes("(")) {
|
|
44
|
+
out = out.replace(re, (...args) => {
|
|
45
|
+
// Replace only the non-group parts; keep named prefixes intact
|
|
46
|
+
const groups = args.slice(1, -2);
|
|
47
|
+
if (groups.length === 1)
|
|
48
|
+
return `${groups[0]}[${label}]`;
|
|
49
|
+
if (groups.length === 2)
|
|
50
|
+
return `${groups[0]}[${label}]${groups[1]}`;
|
|
51
|
+
return `[${label}]`;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
out = out.replace(re, `[${label}]`);
|
|
56
|
+
}
|
|
57
|
+
re.lastIndex = 0; // reset stateful global regexes
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function redactString(s, home) {
|
|
62
|
+
return stripSecrets(stripHome(s, home));
|
|
63
|
+
}
|
|
64
|
+
// ─── Content block traversal ─────────────────────────────────────────────────
|
|
65
|
+
function redactToolInput(input, home) {
|
|
66
|
+
return Object.fromEntries(Object.entries(input).map(([k, v]) => [
|
|
67
|
+
k,
|
|
68
|
+
typeof v === "string" ? redactString(v, home) : v,
|
|
69
|
+
]));
|
|
70
|
+
}
|
|
71
|
+
function redactBlock(block, home) {
|
|
72
|
+
switch (block.type) {
|
|
73
|
+
case "text":
|
|
74
|
+
case "thinking":
|
|
75
|
+
return { ...block, text: redactString(block.text, home) };
|
|
76
|
+
case "tool_use":
|
|
77
|
+
return { ...block, tool_input: redactToolInput(block.tool_input, home) };
|
|
78
|
+
case "tool_result":
|
|
79
|
+
return {
|
|
80
|
+
...block,
|
|
81
|
+
result_content: block.result_content
|
|
82
|
+
? redactString(block.result_content, home)
|
|
83
|
+
: null,
|
|
84
|
+
};
|
|
85
|
+
default:
|
|
86
|
+
return block;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Redact PII and secrets from a trace before it leaves the user's machine.
|
|
92
|
+
*
|
|
93
|
+
* Pass `homeDir: os.homedir()` from the CLI so absolute paths are stripped.
|
|
94
|
+
* The server runs a second pass (without homeDir) as a safety net.
|
|
95
|
+
*/
|
|
96
|
+
export function redactTrace(trace, opts = {}) {
|
|
97
|
+
const home = opts.homeDir ?? "";
|
|
98
|
+
return {
|
|
99
|
+
...trace,
|
|
100
|
+
turns: trace.turns.map((turn) => ({
|
|
101
|
+
...turn,
|
|
102
|
+
content: turn.content.map((b) => redactBlock(b, home)),
|
|
103
|
+
})),
|
|
104
|
+
env_state: trace.env_state
|
|
105
|
+
? {
|
|
106
|
+
...trace.env_state,
|
|
107
|
+
inferred_file_tree: trace.env_state.inferred_file_tree?.map((p) => stripHome(p, home)) ?? null,
|
|
108
|
+
inferred_changed_files: trace.env_state.inferred_changed_files?.map((p) => stripHome(p, home)) ?? null,
|
|
109
|
+
inferred_error_files: trace.env_state.inferred_error_files?.map((p) => stripHome(p, home)) ?? null,
|
|
110
|
+
}
|
|
111
|
+
: null,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./hash.js";
|
|
|
3
3
|
export * from "./scoring.js";
|
|
4
4
|
export * from "./utils.js";
|
|
5
5
|
export * from "./validators.js";
|
|
6
|
+
export * from "./redact.js";
|
|
6
7
|
export { extractClaudeCode } from "./extractors/claude-code.js";
|
|
7
8
|
export { extractCodex } from "./extractors/codex.js";
|
|
8
9
|
export { extractCursor } from "./extractors/cursor.js";
|
package/src/redact.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { NormalizedTrace, ContentBlock, Turn } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface RedactOptions {
|
|
4
|
+
/** Pass os.homedir() from the CLI — strips absolute home paths from all strings. */
|
|
5
|
+
homeDir?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// ─── Secret patterns ────────────────────────────────────────────────────────
|
|
9
|
+
// Ordered from specific → generic to avoid partial matches being swallowed.
|
|
10
|
+
|
|
11
|
+
const SECRET_PATTERNS: Array<{ re: RegExp; label: string }> = [
|
|
12
|
+
// Anthropic
|
|
13
|
+
{ re: /sk-ant-[a-zA-Z0-9\-_]{20,}/g, label: "ANTHROPIC_KEY" },
|
|
14
|
+
// OpenAI (must come before generic sk- catch-all)
|
|
15
|
+
{ re: /sk-proj-[a-zA-Z0-9\-_]{20,}/g, label: "OPENAI_KEY" },
|
|
16
|
+
{ re: /sk-[a-zA-Z0-9]{20,}/g, label: "OPENAI_KEY" },
|
|
17
|
+
// AWS
|
|
18
|
+
{ re: /AKIA[0-9A-Z]{16}/g, label: "AWS_ACCESS_KEY" },
|
|
19
|
+
{ re: /(aws_secret_access_key\s*[=:]\s*)[A-Za-z0-9/+]{40}/gi, label: "AWS_SECRET_KEY" },
|
|
20
|
+
// GitHub
|
|
21
|
+
{ re: /github_pat_[a-zA-Z0-9_]{82}/g, label: "GITHUB_PAT" },
|
|
22
|
+
{ re: /ghp_[a-zA-Z0-9]{36}/g, label: "GITHUB_TOKEN" },
|
|
23
|
+
{ re: /ghs_[a-zA-Z0-9]{36}/g, label: "GITHUB_TOKEN" },
|
|
24
|
+
// Stripe
|
|
25
|
+
{ re: /sk_live_[a-zA-Z0-9]{24,}/g, label: "STRIPE_SECRET_KEY" },
|
|
26
|
+
{ re: /rk_live_[a-zA-Z0-9]{24,}/g, label: "STRIPE_RESTRICTED_KEY" },
|
|
27
|
+
// Resend
|
|
28
|
+
{ re: /re_[a-zA-Z0-9]{32,}/g, label: "RESEND_KEY" },
|
|
29
|
+
// JWTs — eyJ<base64>.<base64>.<base64>
|
|
30
|
+
{ re: /eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]*/g, label: "JWT" },
|
|
31
|
+
// Bearer tokens in Authorization headers
|
|
32
|
+
{ re: /(Bearer\s+)[a-zA-Z0-9\-._~+/]+=*/gi, label: "BEARER_TOKEN" },
|
|
33
|
+
// Passwords in URLs: https://user:PASSWORD@host
|
|
34
|
+
{ re: /(https?:\/\/[^:@\s]+:)[^:@\s]+(@)/g, label: "URL_PASSWORD" },
|
|
35
|
+
// Database DSNs: postgres://user:PASSWORD@host
|
|
36
|
+
{ re: /((?:postgres(?:ql)?|mysql|redis):\/\/[^:]+:)[^@\s]+(@)/g, label: "DB_PASSWORD" },
|
|
37
|
+
// Generic key/secret assignments: API_KEY=abc123... or secret: "abc123..."
|
|
38
|
+
{
|
|
39
|
+
re: /((?:api[_-]?key|api[_-]?secret|access[_-]?token|auth[_-]?token|private[_-]?key|client[_-]?secret)\s*[=:]\s*["']?)[a-zA-Z0-9\-_.+/]{16,}(["']?)/gi,
|
|
40
|
+
label: "SECRET_VALUE",
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// ─── Core string transforms ──────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function stripHome(s: string, home: string): string {
|
|
47
|
+
return home ? s.replaceAll(home, "~") : s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stripSecrets(s: string): string {
|
|
51
|
+
let out = s;
|
|
52
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
53
|
+
// Patterns with capture groups: preserve group 1 (key name), replace group 2 (value)
|
|
54
|
+
if (re.source.includes("(")) {
|
|
55
|
+
out = out.replace(re, (...args) => {
|
|
56
|
+
// Replace only the non-group parts; keep named prefixes intact
|
|
57
|
+
const groups = args.slice(1, -2) as string[];
|
|
58
|
+
if (groups.length === 1) return `${groups[0]}[${label}]`;
|
|
59
|
+
if (groups.length === 2) return `${groups[0]}[${label}]${groups[1]}`;
|
|
60
|
+
return `[${label}]`;
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
out = out.replace(re, `[${label}]`);
|
|
64
|
+
}
|
|
65
|
+
re.lastIndex = 0; // reset stateful global regexes
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function redactString(s: string, home: string): string {
|
|
71
|
+
return stripSecrets(stripHome(s, home));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Content block traversal ─────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
function redactToolInput(input: Record<string, unknown>, home: string): Record<string, unknown> {
|
|
77
|
+
return Object.fromEntries(
|
|
78
|
+
Object.entries(input).map(([k, v]) => [
|
|
79
|
+
k,
|
|
80
|
+
typeof v === "string" ? redactString(v, home) : v,
|
|
81
|
+
])
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function redactBlock(block: ContentBlock, home: string): ContentBlock {
|
|
86
|
+
switch (block.type) {
|
|
87
|
+
case "text":
|
|
88
|
+
case "thinking":
|
|
89
|
+
return { ...block, text: redactString(block.text, home) };
|
|
90
|
+
case "tool_use":
|
|
91
|
+
return { ...block, tool_input: redactToolInput(block.tool_input, home) };
|
|
92
|
+
case "tool_result":
|
|
93
|
+
return {
|
|
94
|
+
...block,
|
|
95
|
+
result_content: block.result_content
|
|
96
|
+
? redactString(block.result_content, home)
|
|
97
|
+
: null,
|
|
98
|
+
};
|
|
99
|
+
default:
|
|
100
|
+
return block;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Redact PII and secrets from a trace before it leaves the user's machine.
|
|
108
|
+
*
|
|
109
|
+
* Pass `homeDir: os.homedir()` from the CLI so absolute paths are stripped.
|
|
110
|
+
* The server runs a second pass (without homeDir) as a safety net.
|
|
111
|
+
*/
|
|
112
|
+
export function redactTrace(
|
|
113
|
+
trace: NormalizedTrace,
|
|
114
|
+
opts: RedactOptions = {}
|
|
115
|
+
): NormalizedTrace {
|
|
116
|
+
const home = opts.homeDir ?? "";
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
...trace,
|
|
120
|
+
turns: trace.turns.map(
|
|
121
|
+
(turn): Turn => ({
|
|
122
|
+
...turn,
|
|
123
|
+
content: turn.content.map((b) => redactBlock(b, home)),
|
|
124
|
+
})
|
|
125
|
+
),
|
|
126
|
+
env_state: trace.env_state
|
|
127
|
+
? {
|
|
128
|
+
...trace.env_state,
|
|
129
|
+
inferred_file_tree:
|
|
130
|
+
trace.env_state.inferred_file_tree?.map((p) => stripHome(p, home)) ?? null,
|
|
131
|
+
inferred_changed_files:
|
|
132
|
+
trace.env_state.inferred_changed_files?.map((p) => stripHome(p, home)) ?? null,
|
|
133
|
+
inferred_error_files:
|
|
134
|
+
trace.env_state.inferred_error_files?.map((p) => stripHome(p, home)) ?? null,
|
|
135
|
+
}
|
|
136
|
+
: null,
|
|
137
|
+
};
|
|
138
|
+
}
|