Files
Aleksey Shakhmatov 0c6e590273 refactor(extensions): simplify lib helpers, tool registration and kit-doctor
Behavior-preserving cleanups:
- extract shared resolveBaseUrl helper for jira/confluence base-URL logic
- extract shared registerTool helper; port jira/gitlab/confluence tools to it
- make scanSecrets declarative (filter/map)
- collapse ok/warn pair into status() and drop double existsSync in kit-doctor

Typecheck clean; npm test 71 PASS, 0 FAIL.
2026-08-07 00:24:04 +03:00

46 lines
1.9 KiB
TypeScript

/**
* Shared secret detection/redaction used by secret-scanner, commit-guard and
* llm-redaction extensions.
*
* CONFIGURE: add entries to SECRET_PATTERNS below. Each pattern has a `name`
* (shown in warnings/audit, never the secret itself) and a `re` (no global
* flag — the global flag is added on demand for redaction).
*/
export interface SecretPattern {
name: string;
re: RegExp;
}
// Patterns are deliberately conservative to limit false positives. The user
// still confirms interactively, so a rare false positive is cheap.
export const SECRET_PATTERNS: SecretPattern[] = [
{ name: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/ },
{ name: "gitlab-pat", re: /\bglpat-[0-9A-Za-z_-]{20,}\b/ },
{ name: "github-pat", re: /\bghp_[0-9A-Za-z]{36}\b/ },
{ name: "github-fine-grained-pat", re: /\bgithub_pat_[0-9A-Za-z_]{22,}\b/ },
{ name: "slack-token", re: /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/ },
{ name: "private-key-block", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/ },
{ name: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/ },
// Generic "secret: value" / "token=value" assignments with a non-trivial value.
{ name: "generic-credential", re: /\b(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*["']?[^\s"']{8,}["']?/i },
];
/** Return the names of secret patterns found in `text` (empty if none). */
export function scanSecrets(text: string): string[] {
if (!text) return [];
// No pattern carries the /g flag, so `test` stays stateless and order is preserved.
return SECRET_PATTERNS.filter((p) => p.re.test(text)).map((p) => p.name);
}
/** Replace every secret occurrence with a labelled placeholder. */
export function redact(text: string): string {
if (!text) return text;
let out = text;
for (const p of SECRET_PATTERNS) {
const g = new RegExp(p.re.source, p.re.flags.includes("g") ? p.re.flags : `${p.re.flags}g`);
out = out.replace(g, `«REDACTED:${p.name}»`);
}
return out;
}