mobbdev 1.2.60 → 1.2.62

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.
@@ -7209,7 +7209,17 @@ import Debug8 from "debug";
7209
7209
 
7210
7210
  // src/utils/sanitize-sensitive-data.ts
7211
7211
  import { OpenRedaction } from "@openredaction/openredaction";
7212
+ var ADO_PAT_PATTERN = {
7213
+ type: "AZURE_DEVOPS_PAT",
7214
+ regex: /\b[a-zA-Z0-9]{20,}JQQJ99[a-zA-Z0-9]{10,52}\b/g,
7215
+ priority: 95,
7216
+ placeholder: "[ADO_PAT_{n}]",
7217
+ description: "Azure DevOps Personal Access Token",
7218
+ severity: "high",
7219
+ validator: (match) => match.length >= 52 && match.length <= 100
7220
+ };
7212
7221
  var openRedaction = new OpenRedaction({
7222
+ customPatterns: [ADO_PAT_PATTERN],
7213
7223
  patterns: [
7214
7224
  // Core Personal Data
7215
7225
  // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
@@ -7280,7 +7290,9 @@ var openRedaction = new OpenRedaction({
7280
7290
  "STRIPE_API_KEY",
7281
7291
  "GOOGLE_API_KEY",
7282
7292
  "FIREBASE_API_KEY",
7283
- "HEROKU_API_KEY",
7293
+ // Removed HEROKU_API_KEY - its regex matches ALL UUIDs ([a-f0-9]{8}-[a-f0-9]{4}-...)
7294
+ // and the validator triggers on "auth" anywhere in context (e.g. "oauth2" in user IDs).
7295
+ // This causes false positives on every UUID when OAuth-related strings are nearby.
7284
7296
  "MAILGUN_API_KEY",
7285
7297
  "SENDGRID_API_KEY",
7286
7298
  "TWILIO_API_KEY",
@@ -7305,7 +7317,11 @@ async function sanitizeDataWithCounts(obj) {
7305
7317
  const counts = {
7306
7318
  detections: { total: 0, high: 0, medium: 0, low: 0 }
7307
7319
  };
7320
+ const MAX_SCAN_LENGTH = 1e5;
7308
7321
  const sanitizeString = async (str) => {
7322
+ if (str.length > MAX_SCAN_LENGTH) {
7323
+ return str;
7324
+ }
7309
7325
  let result = str;
7310
7326
  const piiDetections = openRedaction.scan(str);
7311
7327
  if (piiDetections && piiDetections.total > 0) {
package/dist/index.mjs CHANGED
@@ -13186,7 +13186,17 @@ function getStableComputerName() {
13186
13186
 
13187
13187
  // src/utils/sanitize-sensitive-data.ts
13188
13188
  import { OpenRedaction } from "@openredaction/openredaction";
13189
+ var ADO_PAT_PATTERN = {
13190
+ type: "AZURE_DEVOPS_PAT",
13191
+ regex: /\b[a-zA-Z0-9]{20,}JQQJ99[a-zA-Z0-9]{10,52}\b/g,
13192
+ priority: 95,
13193
+ placeholder: "[ADO_PAT_{n}]",
13194
+ description: "Azure DevOps Personal Access Token",
13195
+ severity: "high",
13196
+ validator: (match) => match.length >= 52 && match.length <= 100
13197
+ };
13189
13198
  var openRedaction = new OpenRedaction({
13199
+ customPatterns: [ADO_PAT_PATTERN],
13190
13200
  patterns: [
13191
13201
  // Core Personal Data
13192
13202
  // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
@@ -13257,7 +13267,9 @@ var openRedaction = new OpenRedaction({
13257
13267
  "STRIPE_API_KEY",
13258
13268
  "GOOGLE_API_KEY",
13259
13269
  "FIREBASE_API_KEY",
13260
- "HEROKU_API_KEY",
13270
+ // Removed HEROKU_API_KEY - its regex matches ALL UUIDs ([a-f0-9]{8}-[a-f0-9]{4}-...)
13271
+ // and the validator triggers on "auth" anywhere in context (e.g. "oauth2" in user IDs).
13272
+ // This causes false positives on every UUID when OAuth-related strings are nearby.
13261
13273
  "MAILGUN_API_KEY",
13262
13274
  "SENDGRID_API_KEY",
13263
13275
  "TWILIO_API_KEY",
@@ -13282,7 +13294,11 @@ async function sanitizeDataWithCounts(obj) {
13282
13294
  const counts = {
13283
13295
  detections: { total: 0, high: 0, medium: 0, low: 0 }
13284
13296
  };
13297
+ const MAX_SCAN_LENGTH = 1e5;
13285
13298
  const sanitizeString = async (str) => {
13299
+ if (str.length > MAX_SCAN_LENGTH) {
13300
+ return str;
13301
+ }
13286
13302
  let result = str;
13287
13303
  const piiDetections = openRedaction.scan(str);
13288
13304
  if (piiDetections && piiDetections.total > 0) {
@@ -16394,9 +16410,11 @@ async function analyzeHandler(args) {
16394
16410
  }
16395
16411
 
16396
16412
  // src/features/claude_code/data_collector.ts
16413
+ import { execFile } from "child_process";
16397
16414
  import { createHash as createHash2 } from "crypto";
16398
16415
  import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
16399
16416
  import path14 from "path";
16417
+ import { promisify } from "util";
16400
16418
  import { z as z33 } from "zod";
16401
16419
  init_client_generates();
16402
16420
 
@@ -16552,7 +16570,10 @@ function createDdBatch(config2) {
16552
16570
  }
16553
16571
  });
16554
16572
  }
16555
- return { enqueue, flush, flushAsync, dispose, createPinoStream };
16573
+ function updateDdTags(ddtags) {
16574
+ config2.ddtags = ddtags;
16575
+ }
16576
+ return { enqueue, flush, flushAsync, dispose, createPinoStream, updateDdTags };
16556
16577
  }
16557
16578
 
16558
16579
  // src/utils/shared-logger/hostname.ts
@@ -16665,6 +16686,11 @@ function createLogger(config2) {
16665
16686
  ddBatch.dispose();
16666
16687
  }
16667
16688
  }
16689
+ function updateDdTags(newDdTags) {
16690
+ if (ddBatch) {
16691
+ ddBatch.updateDdTags(newDdTags);
16692
+ }
16693
+ }
16668
16694
  return {
16669
16695
  info,
16670
16696
  warn,
@@ -16675,14 +16701,23 @@ function createLogger(config2) {
16675
16701
  flushLogs: flushLogs2,
16676
16702
  flushDdAsync,
16677
16703
  disposeDd,
16678
- setScopePath: csStream.setScopePath
16704
+ setScopePath: csStream.setScopePath,
16705
+ updateDdTags
16679
16706
  };
16680
16707
  }
16681
16708
 
16682
16709
  // src/features/claude_code/hook_logger.ts
16683
16710
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
16684
- var CLI_VERSION = true ? "1.2.60" : "unknown";
16711
+ var CLI_VERSION = true ? "1.2.62" : "unknown";
16685
16712
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
16713
+ var claudeCodeVersion;
16714
+ function buildDdTags() {
16715
+ const tags = [`version:${CLI_VERSION}`];
16716
+ if (claudeCodeVersion) {
16717
+ tags.push(`cc_version:${claudeCodeVersion}`);
16718
+ }
16719
+ return tags.join(",");
16720
+ }
16686
16721
  function createHookLogger(scopePath) {
16687
16722
  return createLogger({
16688
16723
  namespace: NAMESPACE,
@@ -16691,7 +16726,7 @@ function createHookLogger(scopePath) {
16691
16726
  apiKey: DD_RUM_TOKEN,
16692
16727
  ddsource: "mobbdev-cli",
16693
16728
  service: "mobbdev-cli-hook",
16694
- ddtags: `version:${CLI_VERSION}`,
16729
+ ddtags: buildDdTags(),
16695
16730
  hostnameMode: "hashed",
16696
16731
  unrefTimer: true
16697
16732
  }
@@ -16700,6 +16735,13 @@ function createHookLogger(scopePath) {
16700
16735
  var logger = createHookLogger();
16701
16736
  var activeScopedLoggers = [];
16702
16737
  var hookLog = logger;
16738
+ function setClaudeCodeVersion(version) {
16739
+ claudeCodeVersion = version;
16740
+ logger.updateDdTags(buildDdTags());
16741
+ }
16742
+ function getClaudeCodeVersion() {
16743
+ return claudeCodeVersion;
16744
+ }
16703
16745
  function flushLogs() {
16704
16746
  logger.flushLogs();
16705
16747
  for (const scoped of activeScopedLoggers) {
@@ -16725,8 +16767,7 @@ import os5 from "os";
16725
16767
  import path13 from "path";
16726
16768
  import chalk11 from "chalk";
16727
16769
  var CLAUDE_SETTINGS_PATH = path13.join(os5.homedir(), ".claude", "settings.json");
16728
- var RECOMMENDED_MATCHER = "Bash|Write|Edit|Agent|Read";
16729
- var STALE_MATCHERS = /* @__PURE__ */ new Set(["", "*", "Edit|Write"]);
16770
+ var RECOMMENDED_MATCHER = "Write|Edit";
16730
16771
  async function claudeSettingsExists() {
16731
16772
  try {
16732
16773
  await fsPromises4.access(CLAUDE_SETTINGS_PATH);
@@ -16758,12 +16799,19 @@ async function autoUpgradeMatcherIfStale() {
16758
16799
  let upgraded = false;
16759
16800
  for (const hook of hooks) {
16760
16801
  const isMobbHook = hook.hooks.some(
16761
- (h) => h.command?.includes("claude-code-process-hook") && (h.command?.includes("mobbdev") || h.command?.includes("mobbdev@"))
16802
+ (h) => h.command?.includes("claude-code-process-hook")
16762
16803
  );
16763
- if (isMobbHook && STALE_MATCHERS.has(hook.matcher)) {
16804
+ if (!isMobbHook) continue;
16805
+ if (hook.matcher !== RECOMMENDED_MATCHER) {
16764
16806
  hook.matcher = RECOMMENDED_MATCHER;
16765
16807
  upgraded = true;
16766
16808
  }
16809
+ for (const h of hook.hooks) {
16810
+ if (!h.async) {
16811
+ h.async = true;
16812
+ upgraded = true;
16813
+ }
16814
+ }
16767
16815
  }
16768
16816
  if (upgraded) {
16769
16817
  await writeClaudeSettings(settings);
@@ -16817,7 +16865,8 @@ async function installMobbHooks(options = {}) {
16817
16865
  hooks: [
16818
16866
  {
16819
16867
  type: "command",
16820
- command
16868
+ command,
16869
+ async: true
16821
16870
  }
16822
16871
  ]
16823
16872
  };
@@ -16842,6 +16891,8 @@ async function installMobbHooks(options = {}) {
16842
16891
  }
16843
16892
 
16844
16893
  // src/features/claude_code/data_collector.ts
16894
+ var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
16895
+ var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
16845
16896
  var GLOBAL_COOLDOWN_MS = 5e3;
16846
16897
  var HOOK_COOLDOWN_MS = 15e3;
16847
16898
  var ACTIVE_LOCK_TTL_MS = 6e4;
@@ -16852,14 +16903,35 @@ var MAX_ENTRIES_PER_INVOCATION = 50;
16852
16903
  var COOLDOWN_KEY = "lastHookRunAt";
16853
16904
  var ACTIVE_KEY = "hookActiveAt";
16854
16905
  var HookDataSchema = z33.object({
16855
- session_id: z33.string(),
16856
- transcript_path: z33.string(),
16857
- cwd: z33.string(),
16906
+ session_id: z33.string().nullish(),
16907
+ transcript_path: z33.string().nullish(),
16908
+ cwd: z33.string().nullish(),
16858
16909
  hook_event_name: z33.string(),
16859
16910
  tool_name: z33.string(),
16860
16911
  tool_input: z33.unknown(),
16861
16912
  tool_response: z33.unknown()
16862
16913
  });
16914
+ var execFileAsync = promisify(execFile);
16915
+ async function detectClaudeCodeVersion() {
16916
+ const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
16917
+ if (cachedCliVersion === packageJson.version) {
16918
+ return configStore.get(CC_VERSION_CACHE_KEY);
16919
+ }
16920
+ try {
16921
+ const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
16922
+ timeout: 3e3,
16923
+ encoding: "utf-8"
16924
+ });
16925
+ const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
16926
+ configStore.set(CC_VERSION_CACHE_KEY, version);
16927
+ configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
16928
+ return version;
16929
+ } catch {
16930
+ configStore.set(CC_VERSION_CACHE_KEY, void 0);
16931
+ configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
16932
+ return void 0;
16933
+ }
16934
+ }
16863
16935
  var STDIN_TIMEOUT_MS = 1e4;
16864
16936
  async function readStdinData() {
16865
16937
  hookLog.debug("Reading stdin data");
@@ -16911,6 +16983,24 @@ async function readStdinData() {
16911
16983
  function validateHookData(data) {
16912
16984
  return HookDataSchema.parse(data);
16913
16985
  }
16986
+ async function extractSessionIdFromTranscript(transcriptPath) {
16987
+ try {
16988
+ const fh = await open4(transcriptPath, "r");
16989
+ try {
16990
+ const buf = Buffer.alloc(4096);
16991
+ const { bytesRead } = await fh.read(buf, 0, 4096, 0);
16992
+ const chunk = buf.toString("utf-8", 0, bytesRead);
16993
+ const firstLine = chunk.split("\n").find((l) => l.trim().length > 0);
16994
+ if (!firstLine) return null;
16995
+ const entry = JSON.parse(firstLine);
16996
+ return entry.sessionId ?? null;
16997
+ } finally {
16998
+ await fh.close();
16999
+ }
17000
+ } catch {
17001
+ return null;
17002
+ }
17003
+ }
16914
17004
  function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
16915
17005
  const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
16916
17006
  const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
@@ -17201,9 +17291,94 @@ async function processAndUploadTranscriptEntries() {
17201
17291
  hookLog.info("Auto-upgraded hook matcher to reduce CPU usage");
17202
17292
  }
17203
17293
  }
17294
+ try {
17295
+ const ccVersion = await detectClaudeCodeVersion();
17296
+ setClaudeCodeVersion(ccVersion);
17297
+ } catch {
17298
+ }
17204
17299
  const rawData = await readStdinData();
17205
- const hookData = validateHookData(rawData);
17206
- const sessionStore = createSessionConfigStore(hookData.session_id);
17300
+ const rawObj = rawData;
17301
+ const hookData = (() => {
17302
+ try {
17303
+ return validateHookData(rawData);
17304
+ } catch (err) {
17305
+ hookLog.error(
17306
+ {
17307
+ data: {
17308
+ hook_event_name: rawObj?.["hook_event_name"],
17309
+ tool_name: rawObj?.["tool_name"],
17310
+ session_id: rawObj?.["session_id"],
17311
+ cwd: rawObj?.["cwd"],
17312
+ keys: rawObj ? Object.keys(rawObj) : []
17313
+ }
17314
+ },
17315
+ `Hook validation failed: ${err.message?.slice(0, 200)}`
17316
+ );
17317
+ throw err;
17318
+ }
17319
+ })();
17320
+ if (!hookData.transcript_path) {
17321
+ hookLog.warn(
17322
+ {
17323
+ data: {
17324
+ hook_event_name: hookData.hook_event_name,
17325
+ tool_name: hookData.tool_name,
17326
+ session_id: hookData.session_id,
17327
+ cwd: hookData.cwd
17328
+ }
17329
+ },
17330
+ "Missing transcript_path \u2014 cannot process hook"
17331
+ );
17332
+ return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17333
+ }
17334
+ let sessionId = hookData.session_id;
17335
+ if (!sessionId) {
17336
+ sessionId = await extractSessionIdFromTranscript(hookData.transcript_path);
17337
+ if (sessionId) {
17338
+ hookLog.warn(
17339
+ {
17340
+ data: {
17341
+ hook_event_name: hookData.hook_event_name,
17342
+ tool_name: hookData.tool_name,
17343
+ cwd: hookData.cwd,
17344
+ extractedSessionId: sessionId
17345
+ }
17346
+ },
17347
+ "Missing session_id in hook data \u2014 extracted from transcript"
17348
+ );
17349
+ } else {
17350
+ hookLog.warn(
17351
+ {
17352
+ data: {
17353
+ hook_event_name: hookData.hook_event_name,
17354
+ tool_name: hookData.tool_name,
17355
+ transcript_path: hookData.transcript_path
17356
+ }
17357
+ },
17358
+ "Missing session_id and could not extract from transcript \u2014 cannot process hook"
17359
+ );
17360
+ return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17361
+ }
17362
+ }
17363
+ if (!hookData.cwd) {
17364
+ hookLog.warn(
17365
+ {
17366
+ data: {
17367
+ hook_event_name: hookData.hook_event_name,
17368
+ tool_name: hookData.tool_name,
17369
+ session_id: sessionId
17370
+ }
17371
+ },
17372
+ "Missing cwd in hook data \u2014 scoped logging and repo URL detection disabled"
17373
+ );
17374
+ }
17375
+ const resolvedHookData = {
17376
+ ...hookData,
17377
+ session_id: sessionId,
17378
+ transcript_path: hookData.transcript_path,
17379
+ cwd: hookData.cwd ?? void 0
17380
+ };
17381
+ const sessionStore = createSessionConfigStore(resolvedHookData.session_id);
17207
17382
  await cleanupStaleSessions(sessionStore);
17208
17383
  const now = Date.now();
17209
17384
  const lastRunAt = sessionStore.get(COOLDOWN_KEY);
@@ -17218,7 +17393,7 @@ async function processAndUploadTranscriptEntries() {
17218
17393
  {
17219
17394
  data: {
17220
17395
  activeDurationMs: activeDuration,
17221
- sessionId: hookData.session_id
17396
+ sessionId: resolvedHookData.session_id
17222
17397
  }
17223
17398
  },
17224
17399
  "Hook still active \u2014 possible slow upload or hung process"
@@ -17228,20 +17403,21 @@ async function processAndUploadTranscriptEntries() {
17228
17403
  }
17229
17404
  sessionStore.set(ACTIVE_KEY, now);
17230
17405
  sessionStore.set(COOLDOWN_KEY, now);
17231
- const log2 = createScopedHookLog(hookData.cwd);
17406
+ const log2 = createScopedHookLog(resolvedHookData.cwd ?? process.cwd());
17232
17407
  log2.info(
17233
17408
  {
17234
17409
  data: {
17235
- sessionId: hookData.session_id,
17236
- toolName: hookData.tool_name,
17237
- hookEvent: hookData.hook_event_name,
17238
- cwd: hookData.cwd
17410
+ sessionId: resolvedHookData.session_id,
17411
+ toolName: resolvedHookData.tool_name,
17412
+ hookEvent: resolvedHookData.hook_event_name,
17413
+ cwd: resolvedHookData.cwd,
17414
+ claudeCodeVersion: getClaudeCodeVersion()
17239
17415
  }
17240
17416
  },
17241
17417
  "Hook data validated"
17242
17418
  );
17243
17419
  try {
17244
- return await processTranscript(hookData, sessionStore, log2);
17420
+ return await processTranscript(resolvedHookData, sessionStore, log2);
17245
17421
  } finally {
17246
17422
  sessionStore.delete(ACTIVE_KEY);
17247
17423
  log2.flushLogs();
@@ -17330,6 +17506,9 @@ async function processTranscript(hookData, sessionStore, log2) {
17330
17506
  rawEntry["message"] = { model: lastSeenModel };
17331
17507
  }
17332
17508
  }
17509
+ if (!rawEntry["sessionId"]) {
17510
+ rawEntry["sessionId"] = hookData.session_id;
17511
+ }
17333
17512
  return {
17334
17513
  platform: "CLAUDE_CODE" /* ClaudeCode */,
17335
17514
  recordId: _recordId,
@@ -17371,7 +17550,8 @@ async function processTranscript(hookData, sessionStore, log2) {
17371
17550
  sessionStore.set(cursorKey, cursor);
17372
17551
  log2.heartbeat("Upload ok", {
17373
17552
  entriesUploaded: entries.length,
17374
- entriesSkipped: filteredOut
17553
+ entriesSkipped: filteredOut,
17554
+ claudeCodeVersion: getClaudeCodeVersion()
17375
17555
  });
17376
17556
  return {
17377
17557
  entriesUploaded: entries.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.2.60",
3
+ "version": "1.2.62",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.mjs",