shiva-code 0.7.7 → 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.
@@ -1,6 +1,7 @@
1
1
  import {
2
+ colors,
2
3
  log
3
- } from "./chunk-Z6NXFC4Q.js";
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) {
@@ -1659,7 +1693,7 @@ hookCommand.command("scan-command").description("Scanne Bash-Befehle auf Package
1659
1693
  }
1660
1694
  });
1661
1695
  hookCommand.command("push").description("Hooks in Cloud sichern").action(async () => {
1662
- const { api } = await import("./client-RPSJP4Z5.js");
1696
+ const { api } = await import("./client-H3JXPT5B.js");
1663
1697
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1664
1698
  if (!isAuthenticated()) {
1665
1699
  log.error("Nicht angemeldet");
@@ -1679,7 +1713,7 @@ hookCommand.command("push").description("Hooks in Cloud sichern").action(async (
1679
1713
  }
1680
1714
  });
1681
1715
  hookCommand.command("pull").description("Hooks aus Cloud laden").option("-f, --force", "Lokale Hooks \xFCberschreiben").action(async (options) => {
1682
- const { api } = await import("./client-RPSJP4Z5.js");
1716
+ const { api } = await import("./client-H3JXPT5B.js");
1683
1717
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1684
1718
  if (!isAuthenticated()) {
1685
1719
  log.error("Nicht angemeldet");
@@ -1711,7 +1745,7 @@ hookCommand.command("pull").description("Hooks aus Cloud laden").option("-f, --f
1711
1745
  }
1712
1746
  });
1713
1747
  hookCommand.command("sync").description("Hooks mit Cloud synchronisieren").action(async () => {
1714
- const { api } = await import("./client-RPSJP4Z5.js");
1748
+ const { api } = await import("./client-H3JXPT5B.js");
1715
1749
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1716
1750
  if (!isAuthenticated()) {
1717
1751
  log.error("Nicht angemeldet");
@@ -1738,9 +1772,9 @@ hookCommand.command("sync").description("Hooks mit Cloud synchronisieren").actio
1738
1772
  }
1739
1773
  });
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
- const { api } = await import("./client-RPSJP4Z5.js");
1775
+ const { api } = await import("./client-H3JXPT5B.js");
1742
1776
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1743
- const { colors } = await import("./logger-E7SC5KUO.js");
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(colors.orange.bold("Cloud Hooks"));
1766
- console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
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 ? colors.green("\u25CF") : colors.dim("\u25CB");
1775
- console.log(` ${enabledIcon} ${colors.bold(hook.id || hook.event)}`);
1776
- console.log(` ${colors.dim("Event:")} ${hook.event}`);
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(` ${colors.dim("Matcher:")} ${hook.matcher}`);
1812
+ console.log(` ${colors2.dim("Matcher:")} ${hook.matcher}`);
1779
1813
  }
1780
- console.log(` ${colors.dim("Type:")} ${hook.type}`);
1781
- console.log(` ${colors.dim("Command:")} ${hook.command}`);
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) {
@@ -1787,7 +1821,7 @@ hookCommand.command("cloud-list").description("Cloud-Hooks auflisten").option("-
1787
1821
  }
1788
1822
  });
1789
1823
  hookCommand.command("cloud-create").description("Neuen Cloud-Hook erstellen").requiredOption("--event <event>", "Event-Typ (PreToolUse, PostToolUse, etc.)").requiredOption("--command <cmd>", "Auszuf\xFChrender Befehl").option("--matcher <pattern>", "Tool-Matcher Pattern").option("--type <type>", "Hook-Typ (command, script)", "command").option("--timeout <ms>", "Timeout in Millisekunden").action(async (options) => {
1790
- const { api } = await import("./client-RPSJP4Z5.js");
1824
+ const { api } = await import("./client-H3JXPT5B.js");
1791
1825
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1792
1826
  if (!isAuthenticated()) {
1793
1827
  log.error("Nicht angemeldet");
@@ -1815,9 +1849,9 @@ hookCommand.command("cloud-create").description("Neuen Cloud-Hook erstellen").re
1815
1849
  }
1816
1850
  });
1817
1851
  hookCommand.command("cloud-test").description("Cloud-Hook testen").argument("<hook-id>", "Hook ID").action(async (hookId) => {
1818
- const { api } = await import("./client-RPSJP4Z5.js");
1852
+ const { api } = await import("./client-H3JXPT5B.js");
1819
1853
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1820
- const { colors } = await import("./logger-E7SC5KUO.js");
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(colors.dim("Output:"));
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(colors.dim("Error:"));
1844
- console.log(colors.red(result.error));
1877
+ console.log(colors2.dim("Error:"));
1878
+ console.log(colors2.red(result.error));
1845
1879
  }
1846
1880
  }
1847
1881
  } catch (error) {
@@ -1850,7 +1884,7 @@ hookCommand.command("cloud-test").description("Cloud-Hook testen").argument("<ho
1850
1884
  }
1851
1885
  });
1852
1886
  hookCommand.command("cloud-delete").description("Cloud-Hook l\xF6schen").argument("<hook-id>", "Hook ID").option("-y, --yes", "Ohne Best\xE4tigung").action(async (hookId, options) => {
1853
- const { api } = await import("./client-RPSJP4Z5.js");
1887
+ const { api } = await import("./client-H3JXPT5B.js");
1854
1888
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1855
1889
  if (!isAuthenticated()) {
1856
1890
  log.error("Nicht angemeldet");
@@ -1885,7 +1919,7 @@ hookCommand.command("cloud-delete").description("Cloud-Hook l\xF6schen").argumen
1885
1919
  }
1886
1920
  });
1887
1921
  hookCommand.command("cloud-toggle").description("Cloud-Hook aktivieren/deaktivieren").argument("<hook-id>", "Hook ID").option("--enable", "Hook aktivieren").option("--disable", "Hook deaktivieren").action(async (hookId, options) => {
1888
- const { api } = await import("./client-RPSJP4Z5.js");
1922
+ const { api } = await import("./client-H3JXPT5B.js");
1889
1923
  const { isAuthenticated } = await import("./config-FGMZONWV.js");
1890
1924
  if (!isAuthenticated()) {
1891
1925
  log.error("Nicht angemeldet");
@@ -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
+ };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  colors,
3
3
  log
4
- } from "./chunk-Z6NXFC4Q.js";
4
+ } from "./chunk-UABU5VVI.js";
5
5
  import {
6
6
  api
7
- } from "./chunk-J5IS642F.js";
7
+ } from "./chunk-KB6M24M3.js";
8
8
  import {
9
9
  clearAuth,
10
10
  getConfig,
@@ -414,44 +414,18 @@ async function loginWithOtp(email) {
414
414
  }
415
415
  ]);
416
416
  const authResponse = await api.verifyOtp(otpResponse.token, otp);
417
+ if (authResponse.requires2FA && authResponse.tempToken) {
418
+ log.newline();
419
+ log.info("2FA ist aktiviert. Bitte verifiziere dich.");
420
+ log.newline();
421
+ const result = await handle2FALoginFlow(authResponse.tempToken, authResponse.email || email);
422
+ return result;
423
+ }
417
424
  if (!authResponse.success || !authResponse.token || !authResponse.user) {
418
425
  log.error(authResponse.message || "Ung\xFCltiger Code");
419
426
  return false;
420
427
  }
421
428
  setAuth(authResponse.token, authResponse.user);
422
- try {
423
- const tfaStatus = await twoFactorService.getStatus();
424
- if (tfaStatus.enabled) {
425
- log.newline();
426
- log.info("2FA ist aktiviert. Bitte verifiziere dich.");
427
- log.newline();
428
- const isDeviceTrusted = await deviceTrustService.isDeviceTrusted();
429
- if (!isDeviceTrusted) {
430
- const verified = await prompt2FAVerification();
431
- if (!verified) {
432
- clearAuth();
433
- log.error("2FA-Verifizierung fehlgeschlagen");
434
- return false;
435
- }
436
- const { trustDevice } = await inquirer.prompt([{
437
- type: "confirm",
438
- name: "trustDevice",
439
- message: "Diesem Ger\xE4t vertrauen? (Kein 2FA bei zuk\xFCnftigen Logins)",
440
- default: true
441
- }]);
442
- if (trustDevice) {
443
- try {
444
- await deviceTrustService.trustDevice();
445
- log.success("Ger\xE4t als vertrauensw\xFCrdig markiert");
446
- } catch {
447
- }
448
- }
449
- } else {
450
- log.dim("Ger\xE4t ist vertrauensw\xFCrdig - 2FA \xFCbersprungen");
451
- }
452
- }
453
- } catch {
454
- }
455
429
  log.newline();
456
430
  log.success(`Angemeldet als ${authResponse.user.email} (${authResponse.user.tier.toUpperCase()})`);
457
431
  return true;
@@ -461,6 +435,58 @@ async function loginWithOtp(email) {
461
435
  return false;
462
436
  }
463
437
  }
438
+ async function handle2FALoginFlow(tempToken, email) {
439
+ const MAX_ATTEMPTS = 3;
440
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
441
+ const { code } = await inquirer.prompt([{
442
+ type: "input",
443
+ name: "code",
444
+ message: "2FA-Code (6 Ziffern oder Backup-Code):",
445
+ validate: (input) => {
446
+ if (/^\d{6}$/.test(input) || /^[A-Za-z0-9-]+$/.test(input)) {
447
+ return true;
448
+ }
449
+ return "Bitte gib einen g\xFCltigen Code ein";
450
+ }
451
+ }]);
452
+ const { rememberDevice } = await inquirer.prompt([{
453
+ type: "confirm",
454
+ name: "rememberDevice",
455
+ message: "Diesem Ger\xE4t vertrauen?",
456
+ default: true
457
+ }]);
458
+ try {
459
+ const result = await api.validate2FALogin({
460
+ tempToken,
461
+ code,
462
+ rememberDevice
463
+ });
464
+ if (result.success && result.token && result.user) {
465
+ setAuth(result.token, result.user);
466
+ if (result.method === "backup" && result.backupCodesRemaining !== void 0) {
467
+ log.warn(`Backup-Code verwendet. Noch ${result.backupCodesRemaining} Codes \xFCbrig.`);
468
+ }
469
+ log.newline();
470
+ log.success(`Angemeldet als ${result.user.email} (${result.user.tier.toUpperCase()})`);
471
+ return true;
472
+ }
473
+ if (attempt < MAX_ATTEMPTS) {
474
+ log.error(`Ung\xFCltiger Code. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
475
+ }
476
+ } catch (error) {
477
+ const message = error instanceof Error ? error.message : "Verifizierung fehlgeschlagen";
478
+ if (message.includes("expired") || message.includes("abgelaufen")) {
479
+ log.error("2FA-Token abgelaufen. Bitte erneut anmelden.");
480
+ return false;
481
+ }
482
+ if (attempt < MAX_ATTEMPTS) {
483
+ log.error(`${message}. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
484
+ }
485
+ }
486
+ }
487
+ log.error("2FA-Verifizierung fehlgeschlagen");
488
+ return false;
489
+ }
464
490
  async function findAvailablePort() {
465
491
  for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
466
492
  const isAvailable = await checkPort(port);
@@ -71,6 +71,16 @@ var ApiClient = class {
71
71
  body: JSON.stringify({ token, otp })
72
72
  });
73
73
  }
74
+ /**
75
+ * Validate 2FA code during login
76
+ * Used when initial login returns requires2FA: true
77
+ */
78
+ async validate2FALogin(data) {
79
+ return this.request("/auth/2fa/login-validate", {
80
+ method: "POST",
81
+ body: JSON.stringify(data)
82
+ });
83
+ }
74
84
  async getCurrentUser() {
75
85
  return this.request("/auth/me");
76
86
  }