brakit 0.9.2 → 0.10.0

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/api.d.ts CHANGED
@@ -468,6 +468,147 @@ declare class MetricsStore {
468
468
  private getOrCreateEndpoint;
469
469
  }
470
470
 
471
+ /** LiveGraph data model — runtime dependency graph types. */
472
+ type NodeType = "endpoint" | "cluster" | "table" | "external";
473
+ type EdgeType = "reads" | "writes" | "fetches" | "calls";
474
+ interface GraphNodeStats {
475
+ requestCount: number;
476
+ avgLatencyMs: number;
477
+ errorRate: number;
478
+ avgQueryCount: number;
479
+ lastSeenAt: number;
480
+ firstSeenAt: number;
481
+ }
482
+ interface SecurityFindingSummary {
483
+ rule: string;
484
+ severity: string;
485
+ title: string;
486
+ count: number;
487
+ }
488
+ interface InsightSummary {
489
+ type: string;
490
+ severity: string;
491
+ title: string;
492
+ }
493
+ interface NodeAnnotations {
494
+ categories?: string[];
495
+ hasAuth?: boolean;
496
+ isMiddleware?: boolean;
497
+ securityFindings?: SecurityFindingSummary[];
498
+ insights?: InsightSummary[];
499
+ openIssueCount?: number;
500
+ p95Ms?: number;
501
+ }
502
+ interface EdgeAnnotations {
503
+ hasIssue?: boolean;
504
+ }
505
+ interface GraphNode {
506
+ id: string;
507
+ type: NodeType;
508
+ label: string;
509
+ children?: string[];
510
+ stats: GraphNodeStats;
511
+ annotations?: NodeAnnotations;
512
+ }
513
+ interface GraphEdgeStats {
514
+ frequency: number;
515
+ avgLatencyMs: number;
516
+ lastSeenAt: number;
517
+ firstSeenAt: number;
518
+ }
519
+ interface GraphEdge {
520
+ id: string;
521
+ source: string;
522
+ target: string;
523
+ type: EdgeType;
524
+ stats: GraphEdgeStats;
525
+ patterns?: string[];
526
+ annotations?: EdgeAnnotations;
527
+ }
528
+ interface LiveGraph {
529
+ nodes: Map<string, GraphNode>;
530
+ edges: Map<string, GraphEdge>;
531
+ metadata: {
532
+ totalObservations: number;
533
+ lastUpdatedAt: number;
534
+ };
535
+ }
536
+ interface ClusterInfo {
537
+ id: string;
538
+ label: string;
539
+ children: string[];
540
+ stats: GraphNodeStats;
541
+ }
542
+ interface GraphApiResponse {
543
+ nodes: GraphNode[];
544
+ edges: GraphEdge[];
545
+ clusters: ClusterInfo[];
546
+ metadata: {
547
+ totalObservations: number;
548
+ lastUpdatedAt: number;
549
+ };
550
+ }
551
+
552
+ type GroupingStrategy = "path" | "auth-boundary" | "data-domain";
553
+
554
+ /**
555
+ * GraphBuilder — listens to EventBus events and incrementally builds
556
+ * a runtime dependency graph of the application.
557
+ *
558
+ * Timing: queries and fetches fire DURING request processing, but
559
+ * request:completed fires AFTER the response is sent. We buffer
560
+ * telemetry events by parentRequestId and process them when the
561
+ * request completes (so we know the endpoint key).
562
+ */
563
+
564
+ declare class GraphBuilder {
565
+ private bus;
566
+ private requestStore;
567
+ private graph;
568
+ /**
569
+ * Buffered telemetry events waiting for their parent request to complete.
570
+ * Key = parentRequestId, value = pending queries/fetches.
571
+ */
572
+ private pending;
573
+ /** Accumulated request categories per endpoint node. */
574
+ private nodeCategories;
575
+ /** Latest analysis snapshot — refreshed on every analysis:updated event. */
576
+ private latestAnalysis;
577
+ private cleanupUnsubs;
578
+ constructor(bus: EventBus, requestStore: RequestStore);
579
+ start(): void;
580
+ stop(): void;
581
+ getGraph(): LiveGraph;
582
+ /**
583
+ * Enrich endpoint nodes with p95 from an external metrics source.
584
+ * Called by the API handler which has access to MetricsStore.
585
+ */
586
+ enrichWithMetrics(getP95: (endpointKey: string) => number | undefined): void;
587
+ getApiResponse(options?: {
588
+ cluster?: string;
589
+ node?: string;
590
+ level?: string;
591
+ grouping?: string;
592
+ }): GraphApiResponse;
593
+ clear(): void;
594
+ private handleAnalysisUpdate;
595
+ private enrichNodesFromAnalysis;
596
+ private handleRequest;
597
+ private handleQuery;
598
+ private handleFetch;
599
+ private processQuery;
600
+ private processFetch;
601
+ computeClusters(strategy?: GroupingStrategy): Map<string, ClusterInfo>;
602
+ private clusterByPath;
603
+ private clusterByAuthBoundary;
604
+ private clusterByDataDomain;
605
+ private buildCluster;
606
+ private getEndpointView;
607
+ private getClusterView;
608
+ private getClusterExpanded;
609
+ private getNodeNeighborhood;
610
+ }
611
+
471
612
  interface Services {
472
613
  bus: EventBus;
473
614
  requestStore: RequestStore;
@@ -478,6 +619,7 @@ interface Services {
478
619
  metricsStore: MetricsStore;
479
620
  issueStore: IssueStore;
480
621
  analysisEngine: AnalysisEngine;
622
+ graphBuilder: GraphBuilder;
481
623
  }
482
624
 
483
625
  declare class AnalysisEngine {
package/dist/api.js CHANGED
@@ -498,22 +498,124 @@ function unwrapResponse(parsed) {
498
498
  }
499
499
 
500
500
  // src/analysis/rules/patterns.ts
501
- var SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
502
- var TOKEN_PARAMS = /^(token|api_key|apiKey|secret|password|access_token|session_id|sessionId)$/;
503
- var SAFE_PARAMS = /^(_rsc|__clerk_handshake|__clerk_db_jwt|callback|code|state|nonce|redirect_uri|utm_|fbclid|gclid)$/;
501
+ var SECRET_KEY_SET = /* @__PURE__ */ new Set([
502
+ "password",
503
+ "passwd",
504
+ "secret",
505
+ "api_key",
506
+ "apiKey",
507
+ "api_secret",
508
+ "apiSecret",
509
+ "private_key",
510
+ "privateKey",
511
+ "client_secret",
512
+ "clientSecret"
513
+ ]);
514
+ var SECRET_KEYS = { test: (s) => SECRET_KEY_SET.has(s) };
515
+ var TOKEN_PARAM_SET = /* @__PURE__ */ new Set([
516
+ "token",
517
+ "api_key",
518
+ "apiKey",
519
+ "secret",
520
+ "password",
521
+ "access_token",
522
+ "session_id",
523
+ "sessionId"
524
+ ]);
525
+ var TOKEN_PARAMS = { test: (s) => TOKEN_PARAM_SET.has(s) };
526
+ var SAFE_PARAM_SET = /* @__PURE__ */ new Set([
527
+ "_rsc",
528
+ "__clerk_handshake",
529
+ "__clerk_db_jwt",
530
+ "callback",
531
+ "code",
532
+ "state",
533
+ "nonce",
534
+ "redirect_uri",
535
+ "utm_",
536
+ "fbclid",
537
+ "gclid"
538
+ ]);
539
+ var SAFE_PARAMS = { test: (s) => SAFE_PARAM_SET.has(s) };
540
+ var INTERNAL_ID_KEY_SET = /* @__PURE__ */ new Set([
541
+ "id",
542
+ "_id",
543
+ "userId",
544
+ "user_id",
545
+ "createdBy",
546
+ "updatedBy",
547
+ "organizationId",
548
+ "org_id",
549
+ "tenantId",
550
+ "tenant_id"
551
+ ]);
552
+ var INTERNAL_ID_KEYS = { test: (s) => INTERNAL_ID_KEY_SET.has(s) };
553
+ var INTERNAL_ID_SUFFIX = {
554
+ test: (s) => s.endsWith("Id") || s.endsWith("_id")
555
+ };
556
+ var SENSITIVE_FIELD_SET = /* @__PURE__ */ new Set([
557
+ "phone",
558
+ "phonenumber",
559
+ "phone_number",
560
+ "ssn",
561
+ "socialsecuritynumber",
562
+ "social_security_number",
563
+ "dateofbirth",
564
+ "date_of_birth",
565
+ "dob",
566
+ "address",
567
+ "streetaddress",
568
+ "street_address",
569
+ "creditcard",
570
+ "credit_card",
571
+ "cardnumber",
572
+ "card_number",
573
+ "bankaccount",
574
+ "bank_account",
575
+ "passport",
576
+ "passportnumber",
577
+ "passport_number",
578
+ "nationalid",
579
+ "national_id"
580
+ ]);
581
+ var SENSITIVE_FIELD_NAMES = {
582
+ test: (s) => SENSITIVE_FIELD_SET.has(s.toLowerCase())
583
+ };
584
+ var SELF_SERVICE_SEGMENTS = /* @__PURE__ */ new Set(["me", "account", "profile", "settings", "self"]);
585
+ var SELF_SERVICE_PATH = {
586
+ test: (path) => {
587
+ const segments = path.toLowerCase().split(/[/?#]/);
588
+ return segments.some((seg) => SELF_SERVICE_SEGMENTS.has(seg));
589
+ }
590
+ };
591
+ var MASKED_LITERALS = ["[REDACTED]", "[FILTERED]", "CHANGE_ME"];
592
+ var MASKED_RE = {
593
+ test: (s) => {
594
+ const upper = s.toUpperCase();
595
+ if (MASKED_LITERALS.some((m) => upper.includes(m))) return true;
596
+ if (s.length > 0 && s.split("").every((c) => c === "*")) return true;
597
+ if (s.length >= 3 && s.split("").every((c) => c === "x" || c === "X")) return true;
598
+ return false;
599
+ }
600
+ };
601
+ var DB_PROTOCOLS = ["postgres://", "mysql://", "mongodb://", "redis://"];
602
+ var DB_CONN_RE = {
603
+ test: (s) => DB_PROTOCOLS.some((p) => s.includes(p))
604
+ };
605
+ var SELECT_STAR_RE = {
606
+ test: (s) => {
607
+ const t = s.trimStart().toUpperCase();
608
+ return t.startsWith("SELECT *") || t.startsWith("SELECT *");
609
+ }
610
+ };
611
+ var SELECT_DOT_STAR_RE = {
612
+ test: (s) => s.toUpperCase().includes(".* FROM")
613
+ };
504
614
  var STACK_TRACE_RE = /at\s+.+\(.+:\d+:\d+\)|at\s+Module\._compile|at\s+Object\.<anonymous>|at\s+processTicksAndRejections|Traceback \(most recent call last\)|File ".+", line \d+/;
505
- var DB_CONN_RE = /(postgres|mysql|mongodb|redis):\/\//;
506
615
  var SQL_FRAGMENT_RE = /\b(SELECT\s+[\w.*]+\s+FROM|INSERT\s+INTO|UPDATE\s+\w+\s+SET|DELETE\s+FROM)\b/i;
507
616
  var SECRET_VAL_RE = /(api_key|apiKey|secret|token)\s*[:=]\s*["']?[A-Za-z0-9_\-.+/]{8,}/;
508
617
  var LOG_SECRET_RE = /(password|secret|token|api_key|apiKey)\s*[:=]\s*["']?[A-Za-z0-9_\-.+/]{8,}/i;
509
- var MASKED_RE = /^\*+$|\[REDACTED\]|\[FILTERED\]|CHANGE_ME|^x{3,}$/i;
510
618
  var EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
511
- var INTERNAL_ID_KEYS = /^(id|_id|userId|user_id|createdBy|updatedBy|organizationId|org_id|tenantId|tenant_id)$/;
512
- var INTERNAL_ID_SUFFIX = /Id$|_id$/;
513
- var SELF_SERVICE_PATH = /\/(?:me|account|profile|settings|self)(?=\/|\?|#|$)/i;
514
- var SENSITIVE_FIELD_NAMES = /^(phone|phoneNumber|phone_number|ssn|socialSecurityNumber|social_security_number|dateOfBirth|date_of_birth|dob|address|streetAddress|street_address|creditCard|credit_card|cardNumber|card_number|bankAccount|bank_account|passport|passportNumber|passport_number|nationalId|national_id)$/i;
515
- var SELECT_STAR_RE = /^SELECT\s+\*/i;
516
- var SELECT_DOT_STAR_RE = /\.\*\s+FROM/i;
517
619
  var RULE_HINTS = {
518
620
  "exposed-secret": "Never include secret fields in API responses. Strip sensitive fields before returning.",
519
621
  "token-in-url": "Pass tokens in the Authorization header, not URL query parameters.",
@@ -1064,6 +1166,7 @@ var DASHBOARD_API_SECURITY = `${DASHBOARD_PREFIX}/api/security`;
1064
1166
  var DASHBOARD_API_TAB = `${DASHBOARD_PREFIX}/api/tab`;
1065
1167
  var DASHBOARD_API_FINDINGS = `${DASHBOARD_PREFIX}/api/findings`;
1066
1168
  var DASHBOARD_API_FINDINGS_REPORT = `${DASHBOARD_PREFIX}/api/findings/report`;
1169
+ var DASHBOARD_API_GRAPH = `${DASHBOARD_PREFIX}/api/graph`;
1067
1170
  var VALID_TABS_TUPLE = [
1068
1171
  "overview",
1069
1172
  "actions",
@@ -1073,7 +1176,8 @@ var VALID_TABS_TUPLE = [
1073
1176
  "errors",
1074
1177
  "logs",
1075
1178
  "performance",
1076
- "security"
1179
+ "security",
1180
+ "graph"
1077
1181
  ];
1078
1182
  var VALID_TABS = new Set(VALID_TABS_TUPLE);
1079
1183
 
@@ -1081,12 +1185,54 @@ var VALID_TABS = new Set(VALID_TABS_TUPLE);
1081
1185
  var RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
1082
1186
 
1083
1187
  // src/utils/endpoint.ts
1084
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1085
- var NUMERIC_ID_RE = /^\d+$/;
1086
- var HEX_HASH_RE = /^[0-9a-f]{12,}$/i;
1087
- var ALPHA_TOKEN_RE = /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z0-9_-]{8,}$/;
1188
+ var UUID_LEN = 36;
1189
+ var MIN_HEX_LEN = 12;
1190
+ var MIN_TOKEN_LEN = 8;
1191
+ function isUUID(s) {
1192
+ if (s.length !== UUID_LEN) return false;
1193
+ for (let i = 0; i < s.length; i++) {
1194
+ const c = s[i];
1195
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
1196
+ if (c !== "-") return false;
1197
+ } else {
1198
+ if (!isHexChar(c)) return false;
1199
+ }
1200
+ }
1201
+ return true;
1202
+ }
1203
+ function isHexChar(c) {
1204
+ const code = c.charCodeAt(0);
1205
+ return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
1206
+ }
1207
+ function isNumericId(s) {
1208
+ if (s.length === 0) return false;
1209
+ for (let i = 0; i < s.length; i++) {
1210
+ const code = s.charCodeAt(i);
1211
+ if (code < 48 || code > 57) return false;
1212
+ }
1213
+ return true;
1214
+ }
1215
+ function isHexHash(s) {
1216
+ if (s.length < MIN_HEX_LEN) return false;
1217
+ for (let i = 0; i < s.length; i++) {
1218
+ if (!isHexChar(s[i])) return false;
1219
+ }
1220
+ return true;
1221
+ }
1222
+ function isAlphanumericToken(s) {
1223
+ if (s.length < MIN_TOKEN_LEN) return false;
1224
+ let hasLetter = false;
1225
+ let hasDigit = false;
1226
+ for (let i = 0; i < s.length; i++) {
1227
+ const code = s.charCodeAt(i);
1228
+ if (code >= 65 && code <= 90 || code >= 97 && code <= 122) hasLetter = true;
1229
+ else if (code >= 48 && code <= 57) hasDigit = true;
1230
+ else if (code !== 95 && code !== 45) return false;
1231
+ }
1232
+ return hasLetter && hasDigit;
1233
+ }
1088
1234
  function isDynamicSegment(segment) {
1089
- return UUID_RE.test(segment) || NUMERIC_ID_RE.test(segment) || HEX_HASH_RE.test(segment) || ALPHA_TOKEN_RE.test(segment);
1235
+ return isUUID(segment) || isNumericId(segment) || isHexHash(segment) || isAlphanumericToken(segment);
1090
1236
  }
1091
1237
  var DYNAMIC_SEGMENT_PLACEHOLDER = ":id";
1092
1238
  function normalizePath(path) {
@@ -1097,9 +1243,12 @@ function normalizePath(path) {
1097
1243
  function getEndpointKey(method, path) {
1098
1244
  return `${method} ${normalizePath(path)}`;
1099
1245
  }
1100
- var ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
1101
1246
  function extractEndpointFromDesc(desc) {
1102
- return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
1247
+ const spaceIdx = desc.indexOf(" ");
1248
+ if (spaceIdx <= 0) return null;
1249
+ const secondSpace = desc.indexOf(" ", spaceIdx + 1);
1250
+ if (secondSpace === -1) return desc;
1251
+ return desc.slice(0, secondSpace);
1103
1252
  }
1104
1253
  function stripQueryString(path) {
1105
1254
  const i = path.indexOf("?");
@@ -1107,6 +1256,10 @@ function stripQueryString(path) {
1107
1256
  }
1108
1257
 
1109
1258
  // src/analysis/categorize.ts
1259
+ function isAuthPath(path) {
1260
+ const lower = path.toLowerCase();
1261
+ return lower.startsWith("/api/auth") || lower.startsWith("/clerk") || lower.startsWith("/api/clerk");
1262
+ }
1110
1263
  function detectCategory(req) {
1111
1264
  const { method, url, statusCode, responseHeaders } = req;
1112
1265
  if (req.isStatic) return "static";
@@ -1115,7 +1268,7 @@ function detectCategory(req) {
1115
1268
  return "auth-handshake";
1116
1269
  }
1117
1270
  const effectivePath = getEffectivePath(req);
1118
- if (/^\/api\/auth/i.test(effectivePath) || /^\/(api\/)?clerk/i.test(effectivePath)) {
1271
+ if (isAuthPath(effectivePath)) {
1119
1272
  return "auth-check";
1120
1273
  }
1121
1274
  if (method === "POST" && !effectivePath.startsWith("/api/")) {
@@ -1205,8 +1358,11 @@ function generateHumanLabel(req, category) {
1205
1358
  return failed ? `${req.method} ${req.path} failed` : `${req.method} ${req.path}`;
1206
1359
  }
1207
1360
  }
1361
+ function stripApiPrefix(s) {
1362
+ return s.startsWith("/api/") ? s.slice(5) : s;
1363
+ }
1208
1364
  function prettifyEndpoint(name) {
1209
- const cleaned = name.replace(/^\/api\//, "").replace(/\//g, " ").replace(/\.\.\./g, "").trim();
1365
+ const cleaned = stripApiPrefix(name).split("/").join(" ").split("...").join("").trim();
1210
1366
  if (!cleaned) return "data";
1211
1367
  return cleaned.split(" ").map((word) => {
1212
1368
  if (word.endsWith("ses") || word.endsWith("us") || word.endsWith("ss"))
@@ -1216,24 +1372,28 @@ function prettifyEndpoint(name) {
1216
1372
  return word;
1217
1373
  }).join(" ");
1218
1374
  }
1375
+ var VERB_MAP = [
1376
+ ["enhance", "Enhanced"],
1377
+ ["generate", "Generated"],
1378
+ ["create", "Created"],
1379
+ ["update", "Updated"],
1380
+ ["delete", "Deleted"],
1381
+ ["remove", "Deleted"],
1382
+ ["send", "Sent"],
1383
+ ["upload", "Uploaded"],
1384
+ ["save", "Saved"],
1385
+ ["submit", "Submitted"],
1386
+ ["login", "Logged in"],
1387
+ ["signin", "Logged in"],
1388
+ ["logout", "Logged out"],
1389
+ ["signout", "Logged out"],
1390
+ ["register", "Registered"],
1391
+ ["signup", "Registered"]
1392
+ ];
1219
1393
  function deriveActionVerb(method, endpointName) {
1220
1394
  const lower = endpointName.toLowerCase();
1221
- const VERB_PATTERNS = [
1222
- [/enhance/, "Enhanced"],
1223
- [/generate/, "Generated"],
1224
- [/create/, "Created"],
1225
- [/update/, "Updated"],
1226
- [/delete|remove/, "Deleted"],
1227
- [/send/, "Sent"],
1228
- [/upload/, "Uploaded"],
1229
- [/save/, "Saved"],
1230
- [/submit/, "Submitted"],
1231
- [/login|signin/, "Logged in"],
1232
- [/logout|signout/, "Logged out"],
1233
- [/register|signup/, "Registered"]
1234
- ];
1235
- for (const [pattern, verb] of VERB_PATTERNS) {
1236
- if (pattern.test(lower)) return verb;
1395
+ for (const [keyword, verb] of VERB_MAP) {
1396
+ if (lower.includes(keyword)) return verb;
1237
1397
  }
1238
1398
  switch (method) {
1239
1399
  case "POST":
@@ -1248,14 +1408,16 @@ function deriveActionVerb(method, endpointName) {
1248
1408
  }
1249
1409
  }
1250
1410
  function getEndpointName(path) {
1251
- const parts = path.replace(/^\/api\//, "").split("/");
1411
+ const parts = stripApiPrefix(path).split("/");
1252
1412
  if (parts.length <= 2) return parts.join("/");
1253
1413
  return parts.map((p) => p.length > ENDPOINT_TRUNCATE_LENGTH ? "..." : p).join("/");
1254
1414
  }
1255
1415
  function prettifyPageName(path) {
1256
- const clean = path.replace(/^\//, "").replace(/\/$/, "");
1416
+ let clean = path;
1417
+ if (clean.startsWith("/")) clean = clean.slice(1);
1418
+ if (clean.endsWith("/")) clean = clean.slice(0, -1);
1257
1419
  if (!clean) return "Home";
1258
- return clean.split("/").map((s) => capitalize(s.replace(/[-_]/g, " "))).join(" ");
1420
+ return clean.split("/").map((s) => capitalize(s.split("-").join(" ").split("_").join(" "))).join(" ");
1259
1421
  }
1260
1422
  function capitalize(s) {
1261
1423
  return s.charAt(0).toUpperCase() + s.slice(1);
@@ -1488,7 +1650,15 @@ var TABLE_RE = /(?:FROM|INTO|UPDATE)\s+(?:"?\w+"?\.)?"?(\w+)"?/i;
1488
1650
  function normalizeSQL(sql) {
1489
1651
  if (!sql) return { op: "OTHER", table: "" };
1490
1652
  const trimmed = sql.trim();
1491
- const keyword = trimmed.split(/\s+/, 1)[0].toUpperCase();
1653
+ let spaceIdx = -1;
1654
+ for (let i = 0; i < trimmed.length; i++) {
1655
+ const c = trimmed[i];
1656
+ if (c === " " || c === " " || c === "\n" || c === "\r") {
1657
+ spaceIdx = i;
1658
+ break;
1659
+ }
1660
+ }
1661
+ const keyword = (spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx)).toUpperCase();
1492
1662
  const op = VALID_OPS.has(keyword) ? keyword : "OTHER";
1493
1663
  const table = trimmed.match(TABLE_RE)?.[1] ?? "";
1494
1664
  return { op, table };
@@ -2241,7 +2411,7 @@ var AnalysisEngine = class {
2241
2411
  };
2242
2412
 
2243
2413
  // src/index.ts
2244
- var VERSION = "0.9.2";
2414
+ var VERSION = "0.10.0";
2245
2415
  export {
2246
2416
  AdapterRegistry,
2247
2417
  AnalysisEngine,
@@ -73,7 +73,7 @@ var init_type_guards = __esm({
73
73
  });
74
74
 
75
75
  // src/constants/labels.ts
76
- var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, VALID_TABS_TUPLE, VALID_TABS, POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS;
76
+ var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, DASHBOARD_API_GRAPH, VALID_TABS_TUPLE, VALID_TABS, POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS;
77
77
  var init_labels = __esm({
78
78
  "src/constants/labels.ts"() {
79
79
  "use strict";
@@ -95,6 +95,7 @@ var init_labels = __esm({
95
95
  DASHBOARD_API_TAB = `${DASHBOARD_PREFIX}/api/tab`;
96
96
  DASHBOARD_API_FINDINGS = `${DASHBOARD_PREFIX}/api/findings`;
97
97
  DASHBOARD_API_FINDINGS_REPORT = `${DASHBOARD_PREFIX}/api/findings/report`;
98
+ DASHBOARD_API_GRAPH = `${DASHBOARD_PREFIX}/api/graph`;
98
99
  VALID_TABS_TUPLE = [
99
100
  "overview",
100
101
  "actions",
@@ -104,7 +105,8 @@ var init_labels = __esm({
104
105
  "errors",
105
106
  "logs",
106
107
  "performance",
107
- "security"
108
+ "security",
109
+ "graph"
108
110
  ];
109
111
  VALID_TABS = new Set(VALID_TABS_TUPLE);
110
112
  POSTHOG_HOST = "https://us.i.posthog.com";
@@ -138,7 +140,7 @@ var init_features = __esm({
138
140
  MAX_TIMELINE_EVENTS = 20;
139
141
  MAX_RESOLVED_DISPLAY = 5;
140
142
  ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
141
- MCP_SERVER_VERSION = "0.9.2";
143
+ MCP_SERVER_VERSION = "0.10.0";
142
144
  RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
143
145
  PORT_MIN = 1;
144
146
  PORT_MAX = 65535;
@@ -1207,7 +1209,8 @@ async function detectPythonFramework(rootDir, hasPyproject, hasRequirements) {
1207
1209
  const content = await readFile3(join2(rootDir, "requirements.txt"), "utf-8");
1208
1210
  const lines = content.toLowerCase().split("\n");
1209
1211
  for (const [dep, fw] of Object.entries(PYTHON_FRAMEWORK_MAP)) {
1210
- if (lines.some((l) => l.startsWith(dep) && (l.length === dep.length || /[=<>~![]/u.test(l[dep.length])))) {
1212
+ const versionChars = /* @__PURE__ */ new Set(["=", "<", ">", "~", "!", "["]);
1213
+ if (lines.some((l) => l.startsWith(dep) && (l.length === dep.length || versionChars.has(l[dep.length])))) {
1211
1214
  return fw;
1212
1215
  }
1213
1216
  }
@@ -1313,20 +1316,115 @@ init_log();
1313
1316
  init_type_guards();
1314
1317
 
1315
1318
  // src/analysis/rules/patterns.ts
1316
- var SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
1317
- var TOKEN_PARAMS = /^(token|api_key|apiKey|secret|password|access_token|session_id|sessionId)$/;
1318
- var SAFE_PARAMS = /^(_rsc|__clerk_handshake|__clerk_db_jwt|callback|code|state|nonce|redirect_uri|utm_|fbclid|gclid)$/;
1319
+ var SECRET_KEY_SET = /* @__PURE__ */ new Set([
1320
+ "password",
1321
+ "passwd",
1322
+ "secret",
1323
+ "api_key",
1324
+ "apiKey",
1325
+ "api_secret",
1326
+ "apiSecret",
1327
+ "private_key",
1328
+ "privateKey",
1329
+ "client_secret",
1330
+ "clientSecret"
1331
+ ]);
1332
+ var SECRET_KEYS = { test: (s) => SECRET_KEY_SET.has(s) };
1333
+ var TOKEN_PARAM_SET = /* @__PURE__ */ new Set([
1334
+ "token",
1335
+ "api_key",
1336
+ "apiKey",
1337
+ "secret",
1338
+ "password",
1339
+ "access_token",
1340
+ "session_id",
1341
+ "sessionId"
1342
+ ]);
1343
+ var TOKEN_PARAMS = { test: (s) => TOKEN_PARAM_SET.has(s) };
1344
+ var SAFE_PARAM_SET = /* @__PURE__ */ new Set([
1345
+ "_rsc",
1346
+ "__clerk_handshake",
1347
+ "__clerk_db_jwt",
1348
+ "callback",
1349
+ "code",
1350
+ "state",
1351
+ "nonce",
1352
+ "redirect_uri",
1353
+ "utm_",
1354
+ "fbclid",
1355
+ "gclid"
1356
+ ]);
1357
+ var SAFE_PARAMS = { test: (s) => SAFE_PARAM_SET.has(s) };
1358
+ var INTERNAL_ID_KEY_SET = /* @__PURE__ */ new Set([
1359
+ "id",
1360
+ "_id",
1361
+ "userId",
1362
+ "user_id",
1363
+ "createdBy",
1364
+ "updatedBy",
1365
+ "organizationId",
1366
+ "org_id",
1367
+ "tenantId",
1368
+ "tenant_id"
1369
+ ]);
1370
+ var INTERNAL_ID_KEYS = { test: (s) => INTERNAL_ID_KEY_SET.has(s) };
1371
+ var INTERNAL_ID_SUFFIX = {
1372
+ test: (s) => s.endsWith("Id") || s.endsWith("_id")
1373
+ };
1374
+ var SENSITIVE_FIELD_SET = /* @__PURE__ */ new Set([
1375
+ "phone",
1376
+ "phonenumber",
1377
+ "phone_number",
1378
+ "ssn",
1379
+ "socialsecuritynumber",
1380
+ "social_security_number",
1381
+ "dateofbirth",
1382
+ "date_of_birth",
1383
+ "dob",
1384
+ "address",
1385
+ "streetaddress",
1386
+ "street_address",
1387
+ "creditcard",
1388
+ "credit_card",
1389
+ "cardnumber",
1390
+ "card_number",
1391
+ "bankaccount",
1392
+ "bank_account",
1393
+ "passport",
1394
+ "passportnumber",
1395
+ "passport_number",
1396
+ "nationalid",
1397
+ "national_id"
1398
+ ]);
1399
+ var SENSITIVE_FIELD_NAMES = {
1400
+ test: (s) => SENSITIVE_FIELD_SET.has(s.toLowerCase())
1401
+ };
1402
+ var SELF_SERVICE_SEGMENTS = /* @__PURE__ */ new Set(["me", "account", "profile", "settings", "self"]);
1403
+ var SELF_SERVICE_PATH = {
1404
+ test: (path) => {
1405
+ const segments = path.toLowerCase().split(/[/?#]/);
1406
+ return segments.some((seg) => SELF_SERVICE_SEGMENTS.has(seg));
1407
+ }
1408
+ };
1409
+ var MASKED_LITERALS = ["[REDACTED]", "[FILTERED]", "CHANGE_ME"];
1410
+ var MASKED_RE = {
1411
+ test: (s) => {
1412
+ const upper = s.toUpperCase();
1413
+ if (MASKED_LITERALS.some((m) => upper.includes(m))) return true;
1414
+ if (s.length > 0 && s.split("").every((c) => c === "*")) return true;
1415
+ if (s.length >= 3 && s.split("").every((c) => c === "x" || c === "X")) return true;
1416
+ return false;
1417
+ }
1418
+ };
1419
+ var DB_PROTOCOLS = ["postgres://", "mysql://", "mongodb://", "redis://"];
1420
+ var DB_CONN_RE = {
1421
+ test: (s) => DB_PROTOCOLS.some((p) => s.includes(p))
1422
+ };
1319
1423
  var STACK_TRACE_RE = /at\s+.+\(.+:\d+:\d+\)|at\s+Module\._compile|at\s+Object\.<anonymous>|at\s+processTicksAndRejections|Traceback \(most recent call last\)|File ".+", line \d+/;
1320
- var DB_CONN_RE = /(postgres|mysql|mongodb|redis):\/\//;
1321
1424
  var SQL_FRAGMENT_RE = /\b(SELECT\s+[\w.*]+\s+FROM|INSERT\s+INTO|UPDATE\s+\w+\s+SET|DELETE\s+FROM)\b/i;
1322
1425
  var SECRET_VAL_RE = /(api_key|apiKey|secret|token)\s*[:=]\s*["']?[A-Za-z0-9_\-.+/]{8,}/;
1323
1426
  var LOG_SECRET_RE = /(password|secret|token|api_key|apiKey)\s*[:=]\s*["']?[A-Za-z0-9_\-.+/]{8,}/i;
1324
- var MASKED_RE = /^\*+$|\[REDACTED\]|\[FILTERED\]|CHANGE_ME|^x{3,}$/i;
1325
1427
  var EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
1326
- var INTERNAL_ID_KEYS = /^(id|_id|userId|user_id|createdBy|updatedBy|organizationId|org_id|tenantId|tenant_id)$/;
1327
- var INTERNAL_ID_SUFFIX = /Id$|_id$/;
1328
- var SELF_SERVICE_PATH = /\/(?:me|account|profile|settings|self)(?=\/|\?|#|$)/i;
1329
- var SENSITIVE_FIELD_NAMES = /^(phone|phoneNumber|phone_number|ssn|socialSecurityNumber|social_security_number|dateOfBirth|date_of_birth|dob|address|streetAddress|street_address|creditCard|credit_card|cardNumber|card_number|bankAccount|bank_account|passport|passportNumber|passport_number|nationalId|national_id)$/i;
1330
1428
  var RULE_HINTS = {
1331
1429
  "exposed-secret": "Never include secret fields in API responses. Strip sensitive fields before returning.",
1332
1430
  "token-in-url": "Pass tokens in the Authorization header, not URL query parameters.",
@@ -1812,7 +1910,7 @@ init_constants();
1812
1910
  init_endpoint();
1813
1911
 
1814
1912
  // src/index.ts
1815
- var VERSION = "0.9.2";
1913
+ var VERSION = "0.10.0";
1816
1914
 
1817
1915
  // src/cli/commands/install.ts
1818
1916
  init_constants();