shiva-code 0.7.8 → 0.7.10
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/{chunk-NM3RPOAT.js → chunk-4OT4OY7L.js} +167 -17
- package/dist/chunk-GGNOX6DN.js +195 -0
- package/dist/{chunk-3BM2KHD4.js → chunk-IWDZCN4S.js} +1 -1
- package/dist/chunk-UABU5VVI.js +246 -0
- package/dist/{hook-YNVEHNHZ.js → hook-CCATCFG7.js} +2 -2
- package/dist/index.js +434 -227
- package/dist/{logger-E7SC5KUO.js → logger-VVWOD6AA.js} +5 -3
- package/dist/{login-DE2U23GS.js → login-DBN2L25M.js} +2 -2
- package/dist/token-detection-K6KCIWAU.js +19 -0
- package/package.json +1 -1
- package/dist/chunk-Z6NXFC4Q.js +0 -118
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
colors,
|
|
2
3
|
log
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UABU5VVI.js";
|
|
4
5
|
import {
|
|
5
6
|
formatContextAsMarkdown,
|
|
6
7
|
generateGitHubContext,
|
|
@@ -1318,7 +1319,7 @@ function removeShivaHooks(eventHooks) {
|
|
|
1318
1319
|
);
|
|
1319
1320
|
}
|
|
1320
1321
|
var hookCommand = new Command("hook").description("Claude Code Hook Integration verwalten");
|
|
1321
|
-
hookCommand.command("install").description("SHIVA Hooks in Claude Code installieren").option("--github", "GitHub Context Injection aktivieren").option("--sync", "Cloud Sync Hooks aktivieren (Standard)").option("--scan", "Package Security Scanning aktivieren").option("--all", "Alle Hooks aktivieren").action((options) => {
|
|
1322
|
+
hookCommand.command("install").description("SHIVA Hooks in Claude Code installieren").option("--github", "GitHub Context Injection aktivieren").option("--sync", "Cloud Sync Hooks aktivieren (Standard)").option("--scan", "Package Security Scanning aktivieren").option("--token-protection", "Token-Schutz aktivieren (erkennt & sch\xFCtzt Secrets)").option("--all", "Alle Hooks aktivieren").action((options) => {
|
|
1322
1323
|
log.brand();
|
|
1323
1324
|
const claudePath = join(homedir(), ".claude");
|
|
1324
1325
|
if (!existsSync(claudePath)) {
|
|
@@ -1334,12 +1335,14 @@ hookCommand.command("install").description("SHIVA Hooks in Claude Code installie
|
|
|
1334
1335
|
if (!settings.hooks) {
|
|
1335
1336
|
settings.hooks = {};
|
|
1336
1337
|
}
|
|
1337
|
-
const installSync = options.sync || options.all || !options.github && !options.scan;
|
|
1338
|
+
const installSync = options.sync || options.all || !options.github && !options.scan && !options.tokenProtection;
|
|
1338
1339
|
const installGithub = options.github || options.all;
|
|
1339
1340
|
const installScan = options.scan || options.all;
|
|
1341
|
+
const installTokenProtection = options.tokenProtection || options.all;
|
|
1340
1342
|
const hasSyncHooks = hasShivaHook(settings.hooks, "SessionStart") || hasShivaHook(settings.hooks, "Stop");
|
|
1341
1343
|
const hasGithubHook = hasShivaContextHook(settings.hooks);
|
|
1342
1344
|
const hasScanHook = hasShivaScanHook(settings.hooks);
|
|
1345
|
+
const hasTokenHook = hasShivaTokenHook(settings.hooks);
|
|
1343
1346
|
if (hasSyncHooks && installSync && !installGithub && !installScan) {
|
|
1344
1347
|
log.warn("SHIVA Sync Hooks sind bereits installiert");
|
|
1345
1348
|
log.newline();
|
|
@@ -1412,6 +1415,21 @@ hookCommand.command("install").description("SHIVA Hooks in Claude Code installie
|
|
|
1412
1415
|
});
|
|
1413
1416
|
installedSomething = true;
|
|
1414
1417
|
}
|
|
1418
|
+
if (installTokenProtection && !hasTokenHook) {
|
|
1419
|
+
if (!settings.hooks.Stop) {
|
|
1420
|
+
settings.hooks.Stop = [];
|
|
1421
|
+
}
|
|
1422
|
+
settings.hooks.Stop.push({
|
|
1423
|
+
hooks: [
|
|
1424
|
+
{
|
|
1425
|
+
type: "command",
|
|
1426
|
+
command: "shiva hook scan-session --quiet",
|
|
1427
|
+
timeout: 30
|
|
1428
|
+
}
|
|
1429
|
+
]
|
|
1430
|
+
});
|
|
1431
|
+
installedSomething = true;
|
|
1432
|
+
}
|
|
1415
1433
|
if (!installedSomething) {
|
|
1416
1434
|
log.warn("Alle ausgew\xE4hlten Hooks sind bereits installiert");
|
|
1417
1435
|
log.newline();
|
|
@@ -1432,6 +1450,9 @@ hookCommand.command("install").description("SHIVA Hooks in Claude Code installie
|
|
|
1432
1450
|
if (installScan || hasScanHook) {
|
|
1433
1451
|
log.tree.item("PreToolUse: Package Security Scanning");
|
|
1434
1452
|
}
|
|
1453
|
+
if (installTokenProtection || hasTokenHook) {
|
|
1454
|
+
log.tree.item("Stop: Token-Schutz (Secrets erkennen & sch\xFCtzen)");
|
|
1455
|
+
}
|
|
1435
1456
|
log.newline();
|
|
1436
1457
|
log.dim("Claude Code wird nun automatisch mit SHIVA integriert.");
|
|
1437
1458
|
log.newline();
|
|
@@ -1452,6 +1473,13 @@ function hasShivaScanHook(hooks) {
|
|
|
1452
1473
|
(entry) => entry.matcher === "Bash" && entry.hooks?.some((h) => h.command?.includes("shiva hook scan-command"))
|
|
1453
1474
|
);
|
|
1454
1475
|
}
|
|
1476
|
+
function hasShivaTokenHook(hooks) {
|
|
1477
|
+
if (!hooks || !hooks.Stop) return false;
|
|
1478
|
+
const eventHooks = hooks.Stop;
|
|
1479
|
+
return eventHooks.some(
|
|
1480
|
+
(entry) => entry.hooks?.some((h) => h.command?.includes("shiva hook scan-session"))
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1455
1483
|
hookCommand.command("uninstall").description("SHIVA Hooks aus Claude Code entfernen").action(() => {
|
|
1456
1484
|
log.brand();
|
|
1457
1485
|
const settings = getClaudeSettings();
|
|
@@ -1503,6 +1531,7 @@ hookCommand.command("status").description("Hook-Status anzeigen").action(() => {
|
|
|
1503
1531
|
const hasStop = hasShivaHook(settings.hooks, "Stop");
|
|
1504
1532
|
const hasGithub = hasShivaContextHook(settings.hooks);
|
|
1505
1533
|
const hasScan = hasShivaScanHook(settings.hooks);
|
|
1534
|
+
const hasToken = hasShivaTokenHook(settings.hooks);
|
|
1506
1535
|
log.plain("Cloud Sync:");
|
|
1507
1536
|
if (hasSessionStart) {
|
|
1508
1537
|
log.listItem("SessionStart: Memories laden", "synced");
|
|
@@ -1528,8 +1557,13 @@ hookCommand.command("status").description("Hook-Status anzeigen").action(() => {
|
|
|
1528
1557
|
} else {
|
|
1529
1558
|
log.listItem("Package Scanning: nicht aktiv", "pending");
|
|
1530
1559
|
}
|
|
1560
|
+
if (hasToken) {
|
|
1561
|
+
log.listItem("Stop: Token-Schutz (Secrets erkennen)", "synced");
|
|
1562
|
+
} else {
|
|
1563
|
+
log.listItem("Token-Schutz: nicht aktiv", "pending");
|
|
1564
|
+
}
|
|
1531
1565
|
log.newline();
|
|
1532
|
-
const allInstalled = hasSessionStart && hasStop && hasGithub && hasScan;
|
|
1566
|
+
const allInstalled = hasSessionStart && hasStop && hasGithub && hasScan && hasToken;
|
|
1533
1567
|
const syncInstalled = hasSessionStart && hasStop;
|
|
1534
1568
|
const anyInstalled = hasSessionStart || hasStop || hasGithub || hasScan;
|
|
1535
1569
|
if (allInstalled) {
|
|
@@ -1740,7 +1774,7 @@ hookCommand.command("sync").description("Hooks mit Cloud synchronisieren").actio
|
|
|
1740
1774
|
hookCommand.command("cloud-list").description("Cloud-Hooks auflisten").option("--event <type>", "Nach Event-Typ filtern").option("--json", "JSON Output").action(async (options) => {
|
|
1741
1775
|
const { api } = await import("./client-H3JXPT5B.js");
|
|
1742
1776
|
const { isAuthenticated } = await import("./config-FGMZONWV.js");
|
|
1743
|
-
const { colors } = await import("./logger-
|
|
1777
|
+
const { colors: colors2 } = await import("./logger-VVWOD6AA.js");
|
|
1744
1778
|
if (!isAuthenticated()) {
|
|
1745
1779
|
log.error("Nicht angemeldet");
|
|
1746
1780
|
log.info("Anmelden mit: shiva login");
|
|
@@ -1762,8 +1796,8 @@ hookCommand.command("cloud-list").description("Cloud-Hooks auflisten").option("-
|
|
|
1762
1796
|
return;
|
|
1763
1797
|
}
|
|
1764
1798
|
log.newline();
|
|
1765
|
-
console.log(
|
|
1766
|
-
console.log(
|
|
1799
|
+
console.log(colors2.orange.bold("Cloud Hooks"));
|
|
1800
|
+
console.log(colors2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1767
1801
|
log.newline();
|
|
1768
1802
|
if (!hooks || Array.isArray(hooks) && hooks.length === 0) {
|
|
1769
1803
|
log.dim("Keine Cloud-Hooks konfiguriert");
|
|
@@ -1771,14 +1805,14 @@ hookCommand.command("cloud-list").description("Cloud-Hooks auflisten").option("-
|
|
|
1771
1805
|
}
|
|
1772
1806
|
const hooksArray = Array.isArray(hooks) ? hooks : Object.values(hooks).flat();
|
|
1773
1807
|
for (const hook of hooksArray) {
|
|
1774
|
-
const enabledIcon = hook.enabled !== false ?
|
|
1775
|
-
console.log(` ${enabledIcon} ${
|
|
1776
|
-
console.log(` ${
|
|
1808
|
+
const enabledIcon = hook.enabled !== false ? colors2.green("\u25CF") : colors2.dim("\u25CB");
|
|
1809
|
+
console.log(` ${enabledIcon} ${colors2.bold(hook.id || hook.event)}`);
|
|
1810
|
+
console.log(` ${colors2.dim("Event:")} ${hook.event}`);
|
|
1777
1811
|
if (hook.matcher) {
|
|
1778
|
-
console.log(` ${
|
|
1812
|
+
console.log(` ${colors2.dim("Matcher:")} ${hook.matcher}`);
|
|
1779
1813
|
}
|
|
1780
|
-
console.log(` ${
|
|
1781
|
-
console.log(` ${
|
|
1814
|
+
console.log(` ${colors2.dim("Type:")} ${hook.type}`);
|
|
1815
|
+
console.log(` ${colors2.dim("Command:")} ${hook.command}`);
|
|
1782
1816
|
log.newline();
|
|
1783
1817
|
}
|
|
1784
1818
|
} catch (error) {
|
|
@@ -1817,7 +1851,7 @@ hookCommand.command("cloud-create").description("Neuen Cloud-Hook erstellen").re
|
|
|
1817
1851
|
hookCommand.command("cloud-test").description("Cloud-Hook testen").argument("<hook-id>", "Hook ID").action(async (hookId) => {
|
|
1818
1852
|
const { api } = await import("./client-H3JXPT5B.js");
|
|
1819
1853
|
const { isAuthenticated } = await import("./config-FGMZONWV.js");
|
|
1820
|
-
const { colors } = await import("./logger-
|
|
1854
|
+
const { colors: colors2 } = await import("./logger-VVWOD6AA.js");
|
|
1821
1855
|
if (!isAuthenticated()) {
|
|
1822
1856
|
log.error("Nicht angemeldet");
|
|
1823
1857
|
log.info("Anmelden mit: shiva login");
|
|
@@ -1833,15 +1867,15 @@ hookCommand.command("cloud-test").description("Cloud-Hook testen").argument("<ho
|
|
|
1833
1867
|
log.success(`Hook erfolgreich (${result.duration}ms)`);
|
|
1834
1868
|
if (result.output) {
|
|
1835
1869
|
log.newline();
|
|
1836
|
-
console.log(
|
|
1870
|
+
console.log(colors2.dim("Output:"));
|
|
1837
1871
|
console.log(result.output);
|
|
1838
1872
|
}
|
|
1839
1873
|
} else {
|
|
1840
1874
|
log.error(`Hook fehlgeschlagen (${result.duration}ms)`);
|
|
1841
1875
|
if (result.error) {
|
|
1842
1876
|
log.newline();
|
|
1843
|
-
console.log(
|
|
1844
|
-
console.log(
|
|
1877
|
+
console.log(colors2.dim("Error:"));
|
|
1878
|
+
console.log(colors2.red(result.error));
|
|
1845
1879
|
}
|
|
1846
1880
|
}
|
|
1847
1881
|
} catch (error) {
|
|
@@ -1911,6 +1945,122 @@ hookCommand.command("cloud-toggle").description("Cloud-Hook aktivieren/deaktivie
|
|
|
1911
1945
|
log.error(error instanceof Error ? error.message : "Unbekannter Fehler");
|
|
1912
1946
|
}
|
|
1913
1947
|
});
|
|
1948
|
+
hookCommand.command("scan-session").description("Session nach sensiblen Tokens scannen (f\xFCr Stop Hook)").option("--quiet", "Keine Ausgabe (f\xFCr Hook)").option("--redact", "Tokens in Session-History maskieren").option("--path <path>", "Session-Datei Pfad").action(async (options) => {
|
|
1949
|
+
const { detectTokens, redactTokens, maskToken } = await import("./token-detection-K6KCIWAU.js");
|
|
1950
|
+
const { api } = await import("./client-H3JXPT5B.js");
|
|
1951
|
+
const { isAuthenticated } = await import("./config-FGMZONWV.js");
|
|
1952
|
+
const { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2, readdirSync, statSync } = await import("fs");
|
|
1953
|
+
const { join: join2 } = await import("path");
|
|
1954
|
+
const { homedir: homedir2 } = await import("os");
|
|
1955
|
+
const quiet = options.quiet;
|
|
1956
|
+
try {
|
|
1957
|
+
let autoRedact = options.redact || false;
|
|
1958
|
+
let autoStore = false;
|
|
1959
|
+
if (isAuthenticated()) {
|
|
1960
|
+
try {
|
|
1961
|
+
const settings = await api.getUserSettings();
|
|
1962
|
+
if (settings.settings?.tokenProtection) {
|
|
1963
|
+
autoRedact = settings.settings.tokenProtection.autoRedact ?? autoRedact;
|
|
1964
|
+
autoStore = settings.settings.tokenProtection.autoStore ?? false;
|
|
1965
|
+
}
|
|
1966
|
+
} catch {
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
const claudeProjectsPath = join2(homedir2(), ".claude", "projects");
|
|
1970
|
+
let sessionFiles = [];
|
|
1971
|
+
if (options.path) {
|
|
1972
|
+
sessionFiles = [options.path];
|
|
1973
|
+
} else if (existsSync2(claudeProjectsPath)) {
|
|
1974
|
+
const oneHourAgo = Date.now() - 60 * 60 * 1e3;
|
|
1975
|
+
try {
|
|
1976
|
+
const projects = readdirSync(claudeProjectsPath);
|
|
1977
|
+
for (const project of projects) {
|
|
1978
|
+
const projectPath = join2(claudeProjectsPath, project);
|
|
1979
|
+
const stat = statSync(projectPath);
|
|
1980
|
+
if (!stat.isDirectory()) continue;
|
|
1981
|
+
const files = readdirSync(projectPath);
|
|
1982
|
+
for (const file of files) {
|
|
1983
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
1984
|
+
const filePath = join2(projectPath, file);
|
|
1985
|
+
const fileStat = statSync(filePath);
|
|
1986
|
+
if (fileStat.mtime.getTime() > oneHourAgo) {
|
|
1987
|
+
sessionFiles.push(filePath);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
} catch {
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
let totalTokensFound = 0;
|
|
1995
|
+
let totalFilesScanned = 0;
|
|
1996
|
+
let totalTokensRedacted = 0;
|
|
1997
|
+
const detectedTokens = [];
|
|
1998
|
+
for (const sessionFile of sessionFiles) {
|
|
1999
|
+
if (!existsSync2(sessionFile)) continue;
|
|
2000
|
+
try {
|
|
2001
|
+
const content = readFileSync2(sessionFile, "utf-8");
|
|
2002
|
+
const tokens = detectTokens(content);
|
|
2003
|
+
if (tokens.length > 0) {
|
|
2004
|
+
totalTokensFound += tokens.length;
|
|
2005
|
+
for (const t of tokens) {
|
|
2006
|
+
detectedTokens.push({
|
|
2007
|
+
file: sessionFile,
|
|
2008
|
+
token: t.token,
|
|
2009
|
+
service: t.pattern.service
|
|
2010
|
+
});
|
|
2011
|
+
if (autoStore && isAuthenticated()) {
|
|
2012
|
+
try {
|
|
2013
|
+
const secretKey = `${t.pattern.name.toUpperCase()}_TOKEN`;
|
|
2014
|
+
await api.addSecret({
|
|
2015
|
+
key: secretKey,
|
|
2016
|
+
value: t.token,
|
|
2017
|
+
description: `Auto-detected ${t.pattern.description}`
|
|
2018
|
+
});
|
|
2019
|
+
} catch {
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
if (autoRedact) {
|
|
2024
|
+
const redactedContent = redactTokens(content);
|
|
2025
|
+
writeFileSync2(sessionFile, redactedContent);
|
|
2026
|
+
totalTokensRedacted += tokens.length;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
totalFilesScanned++;
|
|
2030
|
+
} catch {
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
if (quiet) {
|
|
2034
|
+
process.exit(0);
|
|
2035
|
+
} else {
|
|
2036
|
+
log.newline();
|
|
2037
|
+
if (totalTokensFound === 0) {
|
|
2038
|
+
log.success(`Keine sensiblen Tokens gefunden (${totalFilesScanned} Dateien gescannt)`);
|
|
2039
|
+
} else {
|
|
2040
|
+
console.log(colors.red.bold(`\u26A0\uFE0F ${totalTokensFound} sensible Tokens gefunden!`));
|
|
2041
|
+
log.newline();
|
|
2042
|
+
for (const t of detectedTokens) {
|
|
2043
|
+
console.log(` ${colors.red("!")} ${t.service}: ${colors.dim(maskToken(t.token))}`);
|
|
2044
|
+
}
|
|
2045
|
+
log.newline();
|
|
2046
|
+
if (totalTokensRedacted > 0) {
|
|
2047
|
+
log.success(`${totalTokensRedacted} Tokens in Session-History maskiert`);
|
|
2048
|
+
} else {
|
|
2049
|
+
log.warn("Tokens NICHT maskiert. Verwende --redact oder aktiviere auto-redact in Einstellungen.");
|
|
2050
|
+
}
|
|
2051
|
+
log.newline();
|
|
2052
|
+
log.info("Sichere Tokens mit: shiva secure-token");
|
|
2053
|
+
}
|
|
2054
|
+
log.newline();
|
|
2055
|
+
}
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
if (quiet) {
|
|
2058
|
+
process.exit(0);
|
|
2059
|
+
} else {
|
|
2060
|
+
log.error(error instanceof Error ? error.message : "Fehler beim Scannen");
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
1914
2064
|
|
|
1915
2065
|
export {
|
|
1916
2066
|
packageScanner,
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/utils/token-detection.ts
|
|
2
|
+
var TOKEN_PATTERNS = [
|
|
3
|
+
// NPM
|
|
4
|
+
{
|
|
5
|
+
name: "npm",
|
|
6
|
+
service: "NPM Registry",
|
|
7
|
+
pattern: /npm_[A-Za-z0-9]{36}/g,
|
|
8
|
+
configCommand: (token) => ["npm", "config", "set", "//registry.npmjs.org/:_authToken", token],
|
|
9
|
+
description: "NPM Access Token"
|
|
10
|
+
},
|
|
11
|
+
// GitHub
|
|
12
|
+
{
|
|
13
|
+
name: "github",
|
|
14
|
+
service: "GitHub",
|
|
15
|
+
pattern: /ghp_[A-Za-z0-9]{36}/g,
|
|
16
|
+
description: "GitHub Personal Access Token"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "github_pat",
|
|
20
|
+
service: "GitHub",
|
|
21
|
+
pattern: /github_pat_[A-Za-z0-9_]{80,}/g,
|
|
22
|
+
description: "GitHub Fine-grained Personal Access Token"
|
|
23
|
+
},
|
|
24
|
+
// OpenAI
|
|
25
|
+
{
|
|
26
|
+
name: "openai",
|
|
27
|
+
service: "OpenAI",
|
|
28
|
+
pattern: /sk-[A-Za-z0-9]{48}/g,
|
|
29
|
+
description: "OpenAI API Key"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "openai_proj",
|
|
33
|
+
service: "OpenAI",
|
|
34
|
+
pattern: /sk-proj-[A-Za-z0-9_-]{80,}/g,
|
|
35
|
+
description: "OpenAI Project API Key"
|
|
36
|
+
},
|
|
37
|
+
// Anthropic
|
|
38
|
+
{
|
|
39
|
+
name: "anthropic",
|
|
40
|
+
service: "Anthropic",
|
|
41
|
+
pattern: /sk-ant-[A-Za-z0-9_-]{80,}/g,
|
|
42
|
+
description: "Anthropic API Key"
|
|
43
|
+
},
|
|
44
|
+
// AWS
|
|
45
|
+
{
|
|
46
|
+
name: "aws_access_key",
|
|
47
|
+
service: "AWS",
|
|
48
|
+
pattern: /AKIA[A-Z0-9]{16}/g,
|
|
49
|
+
description: "AWS Access Key ID"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "aws_secret",
|
|
53
|
+
service: "AWS",
|
|
54
|
+
pattern: /(?<![A-Za-z0-9\/+])[A-Za-z0-9\/+=]{40}(?![A-Za-z0-9\/+=])/g,
|
|
55
|
+
description: "AWS Secret Access Key (potential)"
|
|
56
|
+
},
|
|
57
|
+
// Google Cloud
|
|
58
|
+
{
|
|
59
|
+
name: "gcp",
|
|
60
|
+
service: "Google Cloud",
|
|
61
|
+
pattern: /AIza[A-Za-z0-9_-]{35}/g,
|
|
62
|
+
description: "Google Cloud API Key"
|
|
63
|
+
},
|
|
64
|
+
// Stripe
|
|
65
|
+
{
|
|
66
|
+
name: "stripe_secret",
|
|
67
|
+
service: "Stripe",
|
|
68
|
+
pattern: /sk_live_[A-Za-z0-9]{24,}/g,
|
|
69
|
+
description: "Stripe Secret Key (Live)"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "stripe_test",
|
|
73
|
+
service: "Stripe",
|
|
74
|
+
pattern: /sk_test_[A-Za-z0-9]{24,}/g,
|
|
75
|
+
description: "Stripe Secret Key (Test)"
|
|
76
|
+
},
|
|
77
|
+
// Slack
|
|
78
|
+
{
|
|
79
|
+
name: "slack_bot",
|
|
80
|
+
service: "Slack",
|
|
81
|
+
pattern: /xoxb-[A-Za-z0-9-]{50,}/g,
|
|
82
|
+
description: "Slack Bot Token"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "slack_user",
|
|
86
|
+
service: "Slack",
|
|
87
|
+
pattern: /xoxp-[A-Za-z0-9-]{50,}/g,
|
|
88
|
+
description: "Slack User Token"
|
|
89
|
+
},
|
|
90
|
+
// Discord
|
|
91
|
+
{
|
|
92
|
+
name: "discord",
|
|
93
|
+
service: "Discord",
|
|
94
|
+
pattern: /[MN][A-Za-z0-9]{23,}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}/g,
|
|
95
|
+
description: "Discord Bot Token"
|
|
96
|
+
},
|
|
97
|
+
// Twilio
|
|
98
|
+
{
|
|
99
|
+
name: "twilio",
|
|
100
|
+
service: "Twilio",
|
|
101
|
+
pattern: /SK[a-f0-9]{32}/g,
|
|
102
|
+
description: "Twilio API Key"
|
|
103
|
+
},
|
|
104
|
+
// SendGrid
|
|
105
|
+
{
|
|
106
|
+
name: "sendgrid",
|
|
107
|
+
service: "SendGrid",
|
|
108
|
+
pattern: /SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/g,
|
|
109
|
+
description: "SendGrid API Key"
|
|
110
|
+
},
|
|
111
|
+
// Mailchimp
|
|
112
|
+
{
|
|
113
|
+
name: "mailchimp",
|
|
114
|
+
service: "Mailchimp",
|
|
115
|
+
pattern: /[a-f0-9]{32}-us[0-9]{1,2}/g,
|
|
116
|
+
description: "Mailchimp API Key"
|
|
117
|
+
},
|
|
118
|
+
// Heroku
|
|
119
|
+
{
|
|
120
|
+
name: "heroku",
|
|
121
|
+
service: "Heroku",
|
|
122
|
+
pattern: /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
123
|
+
description: "Heroku API Key (UUID format)"
|
|
124
|
+
},
|
|
125
|
+
// Generic JWT (potential sensitive)
|
|
126
|
+
{
|
|
127
|
+
name: "jwt",
|
|
128
|
+
service: "JWT",
|
|
129
|
+
pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
130
|
+
description: "JSON Web Token"
|
|
131
|
+
},
|
|
132
|
+
// Private Keys
|
|
133
|
+
{
|
|
134
|
+
name: "private_key",
|
|
135
|
+
service: "Private Key",
|
|
136
|
+
pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
|
|
137
|
+
description: "Private Key Header"
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
function detectTokens(text) {
|
|
141
|
+
const detected = [];
|
|
142
|
+
for (const pattern of TOKEN_PATTERNS) {
|
|
143
|
+
pattern.pattern.lastIndex = 0;
|
|
144
|
+
let match;
|
|
145
|
+
while ((match = pattern.pattern.exec(text)) !== null) {
|
|
146
|
+
detected.push({
|
|
147
|
+
token: match[0],
|
|
148
|
+
pattern,
|
|
149
|
+
startIndex: match.index,
|
|
150
|
+
endIndex: match.index + match[0].length
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return detected.sort((a, b) => a.startIndex - b.startIndex).filter((item, index, array) => {
|
|
155
|
+
if (index === 0) return true;
|
|
156
|
+
const prev = array[index - 1];
|
|
157
|
+
return item.startIndex >= prev.endIndex;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function containsTokens(text) {
|
|
161
|
+
return detectTokens(text).length > 0;
|
|
162
|
+
}
|
|
163
|
+
function redactTokens(text, replacement = "****REDACTED****") {
|
|
164
|
+
const tokens = detectTokens(text);
|
|
165
|
+
if (tokens.length === 0) return text;
|
|
166
|
+
let result = text;
|
|
167
|
+
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
168
|
+
const { startIndex, endIndex, pattern } = tokens[i];
|
|
169
|
+
const redacted = `[${pattern.service}:${replacement}]`;
|
|
170
|
+
result = result.slice(0, startIndex) + redacted + result.slice(endIndex);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
function maskToken(token) {
|
|
175
|
+
if (token.length <= 12) {
|
|
176
|
+
return "****";
|
|
177
|
+
}
|
|
178
|
+
return `${token.slice(0, 6)}...${token.slice(-4)}`;
|
|
179
|
+
}
|
|
180
|
+
function getPatternByName(name) {
|
|
181
|
+
return TOKEN_PATTERNS.find((p) => p.name === name);
|
|
182
|
+
}
|
|
183
|
+
function getSupportedServices() {
|
|
184
|
+
return [...new Set(TOKEN_PATTERNS.map((p) => p.name))];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {
|
|
188
|
+
TOKEN_PATTERNS,
|
|
189
|
+
detectTokens,
|
|
190
|
+
containsTokens,
|
|
191
|
+
redactTokens,
|
|
192
|
+
maskToken,
|
|
193
|
+
getPatternByName,
|
|
194
|
+
getSupportedServices
|
|
195
|
+
};
|