heyiam 0.1.4 → 0.1.6

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/redact.js ADDED
@@ -0,0 +1,373 @@
1
+ // Redact — scans text for secrets, PII, and sensitive file paths before publish.
2
+ //
3
+ // Two layers:
4
+ // 1. secretlint (community-maintained rules for Anthropic, GitHub, Slack, npm, etc.)
5
+ // 2. Custom regex (fills gaps: OpenAI, Stripe, Google, JWT, Bearer, PII, paths)
6
+ //
7
+ // Two severity levels:
8
+ // HIGH — auto-redacted (known API key prefixes, private keys, connection strings)
9
+ // MEDIUM — flagged for user review (email addresses)
10
+ //
11
+ // Usage:
12
+ // const findings = await scanText(text);
13
+ // const cleaned = await redactText(text);
14
+ // const results = await scanSession(session);
15
+ import { homedir } from "node:os";
16
+ // ── Secretlint integration ─────────────────────────────────────
17
+ let _engine = null;
18
+ let _engineInitFailed = false;
19
+ async function getSecretlintEngine() {
20
+ if (_engine)
21
+ return _engine;
22
+ if (_engineInitFailed)
23
+ return null;
24
+ try {
25
+ const { createEngine } = await import("@secretlint/node");
26
+ _engine = await createEngine({
27
+ color: false,
28
+ formatter: "json",
29
+ maskSecrets: false,
30
+ configFileJSON: {
31
+ rules: [{
32
+ id: "@secretlint/secretlint-rule-preset-recommend",
33
+ }],
34
+ },
35
+ });
36
+ return _engine;
37
+ }
38
+ catch {
39
+ _engineInitFailed = true;
40
+ return null;
41
+ }
42
+ }
43
+ async function secretlintScan(text) {
44
+ const engine = await getSecretlintEngine();
45
+ if (!engine)
46
+ return [];
47
+ try {
48
+ const { ok, output } = await engine.executeOnContent({ content: text, filePath: "/scan.txt" });
49
+ if (ok)
50
+ return []; // no findings
51
+ const results = JSON.parse(output);
52
+ const findings = [];
53
+ for (const file of results) {
54
+ for (const msg of file.messages ?? []) {
55
+ findings.push({
56
+ pattern: msg.ruleId.replace("@secretlint/secretlint-rule-", ""),
57
+ severity: "high",
58
+ category: "secret",
59
+ match: msg.message.length > 60 ? msg.message.slice(0, 50) + "..." : msg.message,
60
+ index: msg.loc?.start?.offset ?? 0,
61
+ });
62
+ }
63
+ }
64
+ return findings;
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ }
70
+ // All regex patterns — used for both scanning AND redaction (find-and-replace).
71
+ // secretlint adds additional detection on top; deduplication prevents double-counting.
72
+ const CUSTOM_SECRET_PATTERNS = [
73
+ // ─── AWS ───────────────────────────────────────────────
74
+ {
75
+ name: "AWS Access Key",
76
+ regex: /\b(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b/g,
77
+ severity: "high",
78
+ category: "secret",
79
+ },
80
+ // ─── GitHub ────────────────────────────────────────────
81
+ {
82
+ name: "GitHub Token",
83
+ regex: /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{36,255}\b/g,
84
+ severity: "high",
85
+ category: "secret",
86
+ },
87
+ // ─── Anthropic ─────────────────────────────────────────
88
+ {
89
+ name: "Anthropic API Key",
90
+ regex: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
91
+ severity: "high",
92
+ category: "secret",
93
+ },
94
+ // ─── OpenAI ────────────────────────────────────────────
95
+ {
96
+ name: "OpenAI API Key",
97
+ regex: /\bsk-(?:proj-)?[A-Za-z0-9]{20,}\b/g,
98
+ severity: "high",
99
+ category: "secret",
100
+ },
101
+ // ─── Slack ─────────────────────────────────────────────
102
+ {
103
+ name: "Slack Token",
104
+ regex: /\bxox[bporas]-[A-Za-z0-9-]{10,}\b/g,
105
+ severity: "high",
106
+ category: "secret",
107
+ },
108
+ // ─── Stripe ────────────────────────────────────────────
109
+ {
110
+ name: "Stripe Key",
111
+ regex: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{20,}\b/g,
112
+ severity: "high",
113
+ category: "secret",
114
+ },
115
+ // ─── Google ────────────────────────────────────────────
116
+ {
117
+ name: "Google API Key",
118
+ regex: /\bAIza[0-9A-Za-z_-]{35}\b/g,
119
+ severity: "high",
120
+ category: "secret",
121
+ },
122
+ // ─── Twilio ────────────────────────────────────────────
123
+ {
124
+ name: "Twilio Auth Token",
125
+ regex: /\bSK[0-9a-f]{32}\b/g,
126
+ severity: "high",
127
+ category: "secret",
128
+ },
129
+ // ─── SendGrid ──────────────────────────────────────────
130
+ {
131
+ name: "SendGrid Key",
132
+ regex: /\bSG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g,
133
+ severity: "high",
134
+ category: "secret",
135
+ },
136
+ // ─── Private keys (PEM) ────────────────────────────────
137
+ {
138
+ name: "Private Key",
139
+ regex: /-----BEGIN[\s\w]*PRIVATE KEY-----[\s\S]{10,}?-----END[\s\w]*PRIVATE KEY-----/g,
140
+ severity: "high",
141
+ category: "secret",
142
+ },
143
+ // ─── Database connection strings with passwords ────────
144
+ {
145
+ name: "Database Connection String",
146
+ regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s:]+:[^\s@]+@[^\s]+/g,
147
+ severity: "high",
148
+ category: "secret",
149
+ },
150
+ // ─── JWT ───────────────────────────────────────────────
151
+ {
152
+ name: "JWT Token",
153
+ regex: /\beyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g,
154
+ severity: "high",
155
+ category: "secret",
156
+ },
157
+ // ─── Generic: KEY=value, SECRET=value, TOKEN=value ─────
158
+ {
159
+ name: "Secret Assignment",
160
+ regex: /\b(?:PASSWORD|SECRET|TOKEN|API_KEY|PRIVATE_KEY|ACCESS_KEY|SECRET_KEY|AUTH_TOKEN|CLIENT_SECRET|ENCRYPTION_KEY|SIGNING_KEY)\s{0,5}[=:]\s{0,5}['"]?[A-Za-z0-9/+=_.-]{8,}['"]?/gi,
161
+ severity: "high",
162
+ category: "secret",
163
+ },
164
+ // ─── Bearer tokens ─────────────────────────────────────
165
+ {
166
+ name: "Bearer Token",
167
+ regex: /\bBearer\s+[A-Za-z0-9_.-]{20,}\b/g,
168
+ severity: "high",
169
+ category: "secret",
170
+ },
171
+ ];
172
+ const PII_PATTERNS = [
173
+ {
174
+ name: "Email Address",
175
+ regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
176
+ severity: "medium",
177
+ category: "pii",
178
+ },
179
+ {
180
+ name: "SSN",
181
+ regex: /\b\d{3}-\d{2}-\d{4}\b/g,
182
+ severity: "high",
183
+ category: "pii",
184
+ },
185
+ ];
186
+ const ALL_CUSTOM_PATTERNS = [...CUSTOM_SECRET_PATTERNS, ...PII_PATTERNS];
187
+ // Email false positives: example/test/noreply addresses common in code
188
+ const EMAIL_ALLOWLIST = [
189
+ /^noreply@/i,
190
+ /^no-reply@/i,
191
+ /^example@/i,
192
+ /^test@/i,
193
+ /^user@example/i,
194
+ /^foo@/i,
195
+ /^bar@/i,
196
+ /^admin@example/i,
197
+ /@example\.(com|org|net)$/i,
198
+ /@test\.(com|org)$/i,
199
+ /@localhost$/i,
200
+ /@users\.noreply\.github\.com$/i,
201
+ ];
202
+ // ── Scanning ───────────────────────────────────────────────────
203
+ function regexScan(text) {
204
+ const findings = [];
205
+ for (const pattern of ALL_CUSTOM_PATTERNS) {
206
+ pattern.regex.lastIndex = 0;
207
+ let match;
208
+ while ((match = pattern.regex.exec(text)) !== null) {
209
+ const matchStr = match[0];
210
+ if (pattern.name === "Email Address") {
211
+ if (EMAIL_ALLOWLIST.some((re) => re.test(matchStr)))
212
+ continue;
213
+ }
214
+ findings.push({
215
+ pattern: pattern.name,
216
+ severity: pattern.severity,
217
+ category: pattern.category,
218
+ match: matchStr.length > 40
219
+ ? matchStr.slice(0, 20) + "..." + matchStr.slice(-10)
220
+ : matchStr,
221
+ index: match.index,
222
+ });
223
+ }
224
+ }
225
+ return findings;
226
+ }
227
+ /** Scan text for secrets and PII using secretlint + custom regex. */
228
+ export async function scanText(text) {
229
+ const [slFindings, regexFindings] = await Promise.all([
230
+ secretlintScan(text),
231
+ Promise.resolve(regexScan(text)),
232
+ ]);
233
+ return deduplicateFindings([...slFindings, ...regexFindings]);
234
+ }
235
+ /** Synchronous scan using only custom regex patterns (no secretlint). */
236
+ export function scanTextSync(text) {
237
+ return regexScan(text);
238
+ }
239
+ /** Replace detected secrets in text. mode='high' redacts only high-severity. */
240
+ export function redactText(text, mode = "high") {
241
+ let result = text;
242
+ for (const pattern of ALL_CUSTOM_PATTERNS) {
243
+ if (mode === "high" && pattern.severity !== "high")
244
+ continue;
245
+ pattern.regex.lastIndex = 0;
246
+ result = result.replace(pattern.regex, (match) => {
247
+ if (pattern.name === "Email Address") {
248
+ if (EMAIL_ALLOWLIST.some((re) => re.test(match)))
249
+ return match;
250
+ }
251
+ return `[REDACTED ${pattern.name.toUpperCase()}]`;
252
+ });
253
+ }
254
+ return result;
255
+ }
256
+ // ── Path stripping ─────────────────────────────────────────────
257
+ const HOME = homedir();
258
+ /** Strip home directory prefix, returning project-relative or ~/rest. */
259
+ export function stripHomePath(filepath, cwd) {
260
+ if (cwd && filepath.startsWith(cwd)) {
261
+ const rel = filepath.slice(cwd.length).replace(/^[/\\]+/, "");
262
+ return rel || filepath;
263
+ }
264
+ if (filepath.startsWith(HOME)) {
265
+ return "~" + filepath.slice(HOME.length);
266
+ }
267
+ return filepath;
268
+ }
269
+ /** Strip home directory and cwd prefixes from all paths in a string. */
270
+ export function stripHomePathsInText(text, cwd) {
271
+ if (cwd) {
272
+ const cwdPattern = escapeRegex(cwd).replace(/\\\//g, "[\\\\/]");
273
+ const cwdRe = new RegExp(cwdPattern + "[/\\\\]?", "g");
274
+ text = text.replace(cwdRe, "");
275
+ }
276
+ const homePattern = escapeRegex(HOME).replace(/\\\//g, "[\\\\/]");
277
+ const homeRe = new RegExp(homePattern + "[/\\\\]?", "g");
278
+ return text.replace(homeRe, "~/");
279
+ }
280
+ // ── Session-level operations ───────────────────────────────────
281
+ /** Scan all string fields in a session object for secrets/PII. */
282
+ export async function scanSession(session) {
283
+ // Collect all string values with their paths
284
+ const strings = [];
285
+ function walk(value, path) {
286
+ if (typeof value === "string" && value.length > 0) {
287
+ strings.push({ value, path });
288
+ }
289
+ else if (Array.isArray(value)) {
290
+ for (let i = 0; i < value.length; i++)
291
+ walk(value[i], `${path}[${i}]`);
292
+ }
293
+ else if (value && typeof value === "object") {
294
+ for (const [k, v] of Object.entries(value))
295
+ walk(v, path ? `${path}.${k}` : k);
296
+ }
297
+ }
298
+ walk(session, "");
299
+ // Batch all strings into one scan for secretlint efficiency
300
+ const allText = strings.map((s) => s.value).join("\n---FIELD_BOUNDARY---\n");
301
+ const allFindings = await scanText(allText);
302
+ // Map findings back to field paths
303
+ const fieldsWithFindings = [];
304
+ let offset = 0;
305
+ for (const { value, path } of strings) {
306
+ const endOffset = offset + value.length;
307
+ const fieldFindings = allFindings.filter((f) => f.index >= offset && f.index < endOffset);
308
+ if (fieldFindings.length > 0)
309
+ fieldsWithFindings.push(path);
310
+ offset = endOffset + "\n---FIELD_BOUNDARY---\n".length;
311
+ }
312
+ return { findings: allFindings, fieldsWithFindings };
313
+ }
314
+ /** Deep-redact all string fields + strip paths. Returns a new object. */
315
+ export function redactSession(session, mode = "high", cwd) {
316
+ return deepRedact(session, mode, cwd);
317
+ }
318
+ function deepRedact(value, mode, cwd) {
319
+ if (typeof value === "string") {
320
+ let result = redactText(value, mode);
321
+ result = stripHomePathsInText(result, cwd);
322
+ return result;
323
+ }
324
+ if (Array.isArray(value)) {
325
+ return value.map((v) => deepRedact(v, mode, cwd));
326
+ }
327
+ if (value && typeof value === "object") {
328
+ const out = {};
329
+ for (const [k, v] of Object.entries(value)) {
330
+ out[k] = deepRedact(v, mode, cwd);
331
+ }
332
+ return out;
333
+ }
334
+ return value;
335
+ }
336
+ // ── Helpers ────────────────────────────────────────────────────
337
+ function escapeRegex(s) {
338
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
339
+ }
340
+ /** Deduplicate findings by (pattern, match) pair. */
341
+ export function deduplicateFindings(findings) {
342
+ const seen = new Set();
343
+ return findings.filter((f) => {
344
+ const key = `${f.pattern}::${f.match}`;
345
+ if (seen.has(key))
346
+ return false;
347
+ seen.add(key);
348
+ return true;
349
+ });
350
+ }
351
+ /** Format findings for CLI warning output. */
352
+ export function formatFindings(findings) {
353
+ const deduped = deduplicateFindings(findings);
354
+ if (deduped.length === 0)
355
+ return "";
356
+ const high = deduped.filter((f) => f.severity === "high");
357
+ const medium = deduped.filter((f) => f.severity === "medium");
358
+ const lines = [];
359
+ if (high.length > 0) {
360
+ lines.push(` ${high.length} secret(s) auto-redacted:`);
361
+ for (const f of high) {
362
+ lines.push(` - ${f.pattern}: ${f.match}`);
363
+ }
364
+ }
365
+ if (medium.length > 0) {
366
+ lines.push(` ${medium.length} potential PII flagged:`);
367
+ for (const f of medium) {
368
+ lines.push(` - ${f.pattern}: ${f.match}`);
369
+ }
370
+ }
371
+ return lines.join("\n");
372
+ }
373
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Find the first available Chrome binary on the system.
3
+ * Returns the path or null if not found.
4
+ */
5
+ export declare function findChrome(): string | null;
6
+ /**
7
+ * Capture a screenshot of a URL using headless Chrome.
8
+ * Returns the local file path on success, or null if Chrome is unavailable or capture fails.
9
+ */
10
+ export declare function captureScreenshot(url: string, slug: string): Promise<string | null>;
@@ -0,0 +1,80 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir, platform } from 'node:os';
5
+ const SCREENSHOTS_DIR = join(homedir(), '.config', 'heyiam', 'screenshots');
6
+ /** Known Chrome binary paths by platform */
7
+ const CHROME_PATHS = {
8
+ darwin: [
9
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
10
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
11
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
12
+ ],
13
+ linux: [
14
+ '/usr/bin/google-chrome',
15
+ '/usr/bin/google-chrome-stable',
16
+ '/usr/bin/chromium',
17
+ '/usr/bin/chromium-browser',
18
+ '/snap/bin/chromium',
19
+ ],
20
+ win32: [
21
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
22
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
23
+ ],
24
+ };
25
+ /**
26
+ * Find the first available Chrome binary on the system.
27
+ * Returns the path or null if not found.
28
+ */
29
+ export function findChrome() {
30
+ const paths = CHROME_PATHS[platform()] ?? [];
31
+ for (const p of paths) {
32
+ if (existsSync(p))
33
+ return p;
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Capture a screenshot of a URL using headless Chrome.
39
+ * Returns the local file path on success, or null if Chrome is unavailable or capture fails.
40
+ */
41
+ export async function captureScreenshot(url, slug) {
42
+ const chrome = findChrome();
43
+ if (!chrome)
44
+ return null;
45
+ mkdirSync(SCREENSHOTS_DIR, { recursive: true });
46
+ const outPath = join(SCREENSHOTS_DIR, `${slug}.png`);
47
+ try {
48
+ await new Promise((resolve, reject) => {
49
+ execFile(chrome, [
50
+ '--headless=new',
51
+ '--disable-gpu',
52
+ '--no-sandbox',
53
+ '--disable-software-rasterizer',
54
+ `--screenshot=${outPath}`,
55
+ '--window-size=1280,800',
56
+ '--hide-scrollbars',
57
+ '--disable-extensions',
58
+ '--disable-background-networking',
59
+ '--disable-sync',
60
+ '--disable-translate',
61
+ '--disable-default-apps',
62
+ '--mute-audio',
63
+ '--no-first-run',
64
+ url,
65
+ ], { timeout: 30_000 }, (err) => {
66
+ if (err)
67
+ reject(err);
68
+ else
69
+ resolve();
70
+ });
71
+ });
72
+ if (existsSync(outPath))
73
+ return outPath;
74
+ return null;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAE5E,4CAA4C;AAC5C,MAAM,YAAY,GAA6B;IAC7C,MAAM,EAAE;QACN,8DAA8D;QAC9D,oDAAoD;QACpD,4EAA4E;KAC7E;IACD,KAAK,EAAE;QACL,wBAAwB;QACxB,+BAA+B;QAC/B,mBAAmB;QACnB,2BAA2B;QAC3B,oBAAoB;KACrB;IACD,KAAK,EAAE;QACL,4DAA4D;QAC5D,kEAAkE;KACnE;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAY;IAC/D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,SAAS,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAErD,IAAI,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,QAAQ,CAAC,MAAM,EAAE;gBACf,gBAAgB;gBAChB,eAAe;gBACf,cAAc;gBACd,+BAA+B;gBAC/B,gBAAgB,OAAO,EAAE;gBACzB,wBAAwB;gBACxB,mBAAmB;gBACnB,sBAAsB;gBACtB,iCAAiC;gBACjC,gBAAgB;gBAChB,qBAAqB;gBACrB,wBAAwB;gBACxB,cAAc;gBACd,gBAAgB;gBAChB,GAAG;aACJ,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC9B,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}