mobbdev 1.2.60 → 1.2.65

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.
@@ -5518,21 +5518,6 @@ Make sure the input is not:
5518
5518
  - \`Runtime.getRuntime().exec(new String[] {"perl", "-e", "print '" + input + "'"});\``,
5519
5519
  guidance: () => ""
5520
5520
  },
5521
- isPlainCommandArgument: {
5522
- content: () => "Is the input an argument of a plain command?",
5523
- description: () => `Examples for "yes" answer:
5524
-
5525
- - \`Runtime.getRuntime().exec("git clone " + input);\`
5526
- - \`Runtime.getRuntime().exec("curl " + input);\`
5527
- - \`Runtime.getRuntime().exec("cat " + input);\`
5528
-
5529
- Examples for "no" answer:
5530
-
5531
- - \`Runtime.getRuntime().exec("cmd /c " + input);\`
5532
- - \`Runtime.getRuntime().exec("sh -c " + input);\`
5533
- - \`Runtime.getRuntime().exec("perl -e " + input);\``,
5534
- guidance: () => ""
5535
- },
5536
5521
  installApacheCommonsText: {
5537
5522
  content: () => "Is the Apache Commons library (org.apache.commons) included in your project, if not, can you add it?",
5538
5523
  description: () => "Apache Commons Text is a library focused on algorithms working on strings.",
@@ -7209,7 +7194,17 @@ import Debug8 from "debug";
7209
7194
 
7210
7195
  // src/utils/sanitize-sensitive-data.ts
7211
7196
  import { OpenRedaction } from "@openredaction/openredaction";
7197
+ var ADO_PAT_PATTERN = {
7198
+ type: "AZURE_DEVOPS_PAT",
7199
+ regex: /\b[a-zA-Z0-9]{20,}JQQJ99[a-zA-Z0-9]{10,52}\b/g,
7200
+ priority: 95,
7201
+ placeholder: "[ADO_PAT_{n}]",
7202
+ description: "Azure DevOps Personal Access Token",
7203
+ severity: "high",
7204
+ validator: (match) => match.length >= 52 && match.length <= 100
7205
+ };
7212
7206
  var openRedaction = new OpenRedaction({
7207
+ customPatterns: [ADO_PAT_PATTERN],
7213
7208
  patterns: [
7214
7209
  // Core Personal Data
7215
7210
  // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
@@ -7280,7 +7275,9 @@ var openRedaction = new OpenRedaction({
7280
7275
  "STRIPE_API_KEY",
7281
7276
  "GOOGLE_API_KEY",
7282
7277
  "FIREBASE_API_KEY",
7283
- "HEROKU_API_KEY",
7278
+ // Removed HEROKU_API_KEY - its regex matches ALL UUIDs ([a-f0-9]{8}-[a-f0-9]{4}-...)
7279
+ // and the validator triggers on "auth" anywhere in context (e.g. "oauth2" in user IDs).
7280
+ // This causes false positives on every UUID when OAuth-related strings are nearby.
7284
7281
  "MAILGUN_API_KEY",
7285
7282
  "SENDGRID_API_KEY",
7286
7283
  "TWILIO_API_KEY",
@@ -7305,7 +7302,11 @@ async function sanitizeDataWithCounts(obj) {
7305
7302
  const counts = {
7306
7303
  detections: { total: 0, high: 0, medium: 0, low: 0 }
7307
7304
  };
7305
+ const MAX_SCAN_LENGTH = 1e5;
7308
7306
  const sanitizeString = async (str) => {
7307
+ if (str.length > MAX_SCAN_LENGTH) {
7308
+ return str;
7309
+ }
7309
7310
  let result = str;
7310
7311
  const piiDetections = openRedaction.scan(str);
7311
7312
  if (piiDetections && piiDetections.total > 0) {
package/dist/index.mjs CHANGED
@@ -5269,21 +5269,6 @@ Make sure the input is not:
5269
5269
  - \`Runtime.getRuntime().exec(new String[] {"perl", "-e", "print '" + input + "'"});\``,
5270
5270
  guidance: () => ""
5271
5271
  },
5272
- isPlainCommandArgument: {
5273
- content: () => "Is the input an argument of a plain command?",
5274
- description: () => `Examples for "yes" answer:
5275
-
5276
- - \`Runtime.getRuntime().exec("git clone " + input);\`
5277
- - \`Runtime.getRuntime().exec("curl " + input);\`
5278
- - \`Runtime.getRuntime().exec("cat " + input);\`
5279
-
5280
- Examples for "no" answer:
5281
-
5282
- - \`Runtime.getRuntime().exec("cmd /c " + input);\`
5283
- - \`Runtime.getRuntime().exec("sh -c " + input);\`
5284
- - \`Runtime.getRuntime().exec("perl -e " + input);\``,
5285
- guidance: () => ""
5286
- },
5287
5272
  installApacheCommonsText: {
5288
5273
  content: () => "Is the Apache Commons library (org.apache.commons) included in your project, if not, can you add it?",
5289
5274
  description: () => "Apache Commons Text is a library focused on algorithms working on strings.",
@@ -13186,7 +13171,17 @@ function getStableComputerName() {
13186
13171
 
13187
13172
  // src/utils/sanitize-sensitive-data.ts
13188
13173
  import { OpenRedaction } from "@openredaction/openredaction";
13174
+ var ADO_PAT_PATTERN = {
13175
+ type: "AZURE_DEVOPS_PAT",
13176
+ regex: /\b[a-zA-Z0-9]{20,}JQQJ99[a-zA-Z0-9]{10,52}\b/g,
13177
+ priority: 95,
13178
+ placeholder: "[ADO_PAT_{n}]",
13179
+ description: "Azure DevOps Personal Access Token",
13180
+ severity: "high",
13181
+ validator: (match) => match.length >= 52 && match.length <= 100
13182
+ };
13189
13183
  var openRedaction = new OpenRedaction({
13184
+ customPatterns: [ADO_PAT_PATTERN],
13190
13185
  patterns: [
13191
13186
  // Core Personal Data
13192
13187
  // Removed EMAIL - causes false positives in code/test snippets (e.g. --author="Eve Author <eve@example.com>")
@@ -13257,7 +13252,9 @@ var openRedaction = new OpenRedaction({
13257
13252
  "STRIPE_API_KEY",
13258
13253
  "GOOGLE_API_KEY",
13259
13254
  "FIREBASE_API_KEY",
13260
- "HEROKU_API_KEY",
13255
+ // Removed HEROKU_API_KEY - its regex matches ALL UUIDs ([a-f0-9]{8}-[a-f0-9]{4}-...)
13256
+ // and the validator triggers on "auth" anywhere in context (e.g. "oauth2" in user IDs).
13257
+ // This causes false positives on every UUID when OAuth-related strings are nearby.
13261
13258
  "MAILGUN_API_KEY",
13262
13259
  "SENDGRID_API_KEY",
13263
13260
  "TWILIO_API_KEY",
@@ -13282,7 +13279,11 @@ async function sanitizeDataWithCounts(obj) {
13282
13279
  const counts = {
13283
13280
  detections: { total: 0, high: 0, medium: 0, low: 0 }
13284
13281
  };
13282
+ const MAX_SCAN_LENGTH = 1e5;
13285
13283
  const sanitizeString = async (str) => {
13284
+ if (str.length > MAX_SCAN_LENGTH) {
13285
+ return str;
13286
+ }
13286
13287
  let result = str;
13287
13288
  const piiDetections = openRedaction.scan(str);
13288
13289
  if (piiDetections && piiDetections.total > 0) {
@@ -16394,9 +16395,11 @@ async function analyzeHandler(args) {
16394
16395
  }
16395
16396
 
16396
16397
  // src/features/claude_code/data_collector.ts
16398
+ import { execFile } from "child_process";
16397
16399
  import { createHash as createHash2 } from "crypto";
16398
16400
  import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
16399
16401
  import path14 from "path";
16402
+ import { promisify } from "util";
16400
16403
  import { z as z33 } from "zod";
16401
16404
  init_client_generates();
16402
16405
 
@@ -16552,7 +16555,10 @@ function createDdBatch(config2) {
16552
16555
  }
16553
16556
  });
16554
16557
  }
16555
- return { enqueue, flush, flushAsync, dispose, createPinoStream };
16558
+ function updateDdTags(ddtags) {
16559
+ config2.ddtags = ddtags;
16560
+ }
16561
+ return { enqueue, flush, flushAsync, dispose, createPinoStream, updateDdTags };
16556
16562
  }
16557
16563
 
16558
16564
  // src/utils/shared-logger/hostname.ts
@@ -16665,6 +16671,11 @@ function createLogger(config2) {
16665
16671
  ddBatch.dispose();
16666
16672
  }
16667
16673
  }
16674
+ function updateDdTags(newDdTags) {
16675
+ if (ddBatch) {
16676
+ ddBatch.updateDdTags(newDdTags);
16677
+ }
16678
+ }
16668
16679
  return {
16669
16680
  info,
16670
16681
  warn,
@@ -16675,14 +16686,23 @@ function createLogger(config2) {
16675
16686
  flushLogs: flushLogs2,
16676
16687
  flushDdAsync,
16677
16688
  disposeDd,
16678
- setScopePath: csStream.setScopePath
16689
+ setScopePath: csStream.setScopePath,
16690
+ updateDdTags
16679
16691
  };
16680
16692
  }
16681
16693
 
16682
16694
  // src/features/claude_code/hook_logger.ts
16683
16695
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
16684
- var CLI_VERSION = true ? "1.2.60" : "unknown";
16696
+ var CLI_VERSION = true ? "1.2.65" : "unknown";
16685
16697
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
16698
+ var claudeCodeVersion;
16699
+ function buildDdTags() {
16700
+ const tags = [`version:${CLI_VERSION}`];
16701
+ if (claudeCodeVersion) {
16702
+ tags.push(`cc_version:${claudeCodeVersion}`);
16703
+ }
16704
+ return tags.join(",");
16705
+ }
16686
16706
  function createHookLogger(scopePath) {
16687
16707
  return createLogger({
16688
16708
  namespace: NAMESPACE,
@@ -16691,7 +16711,7 @@ function createHookLogger(scopePath) {
16691
16711
  apiKey: DD_RUM_TOKEN,
16692
16712
  ddsource: "mobbdev-cli",
16693
16713
  service: "mobbdev-cli-hook",
16694
- ddtags: `version:${CLI_VERSION}`,
16714
+ ddtags: buildDdTags(),
16695
16715
  hostnameMode: "hashed",
16696
16716
  unrefTimer: true
16697
16717
  }
@@ -16700,6 +16720,13 @@ function createHookLogger(scopePath) {
16700
16720
  var logger = createHookLogger();
16701
16721
  var activeScopedLoggers = [];
16702
16722
  var hookLog = logger;
16723
+ function setClaudeCodeVersion(version) {
16724
+ claudeCodeVersion = version;
16725
+ logger.updateDdTags(buildDdTags());
16726
+ }
16727
+ function getClaudeCodeVersion() {
16728
+ return claudeCodeVersion;
16729
+ }
16703
16730
  function flushLogs() {
16704
16731
  logger.flushLogs();
16705
16732
  for (const scoped of activeScopedLoggers) {
@@ -16725,8 +16752,7 @@ import os5 from "os";
16725
16752
  import path13 from "path";
16726
16753
  import chalk11 from "chalk";
16727
16754
  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"]);
16755
+ var RECOMMENDED_MATCHER = "Write|Edit";
16730
16756
  async function claudeSettingsExists() {
16731
16757
  try {
16732
16758
  await fsPromises4.access(CLAUDE_SETTINGS_PATH);
@@ -16758,12 +16784,19 @@ async function autoUpgradeMatcherIfStale() {
16758
16784
  let upgraded = false;
16759
16785
  for (const hook of hooks) {
16760
16786
  const isMobbHook = hook.hooks.some(
16761
- (h) => h.command?.includes("claude-code-process-hook") && (h.command?.includes("mobbdev") || h.command?.includes("mobbdev@"))
16787
+ (h) => h.command?.includes("claude-code-process-hook")
16762
16788
  );
16763
- if (isMobbHook && STALE_MATCHERS.has(hook.matcher)) {
16789
+ if (!isMobbHook) continue;
16790
+ if (hook.matcher !== RECOMMENDED_MATCHER) {
16764
16791
  hook.matcher = RECOMMENDED_MATCHER;
16765
16792
  upgraded = true;
16766
16793
  }
16794
+ for (const h of hook.hooks) {
16795
+ if (!h.async) {
16796
+ h.async = true;
16797
+ upgraded = true;
16798
+ }
16799
+ }
16767
16800
  }
16768
16801
  if (upgraded) {
16769
16802
  await writeClaudeSettings(settings);
@@ -16817,7 +16850,8 @@ async function installMobbHooks(options = {}) {
16817
16850
  hooks: [
16818
16851
  {
16819
16852
  type: "command",
16820
- command
16853
+ command,
16854
+ async: true
16821
16855
  }
16822
16856
  ]
16823
16857
  };
@@ -16842,6 +16876,8 @@ async function installMobbHooks(options = {}) {
16842
16876
  }
16843
16877
 
16844
16878
  // src/features/claude_code/data_collector.ts
16879
+ var CC_VERSION_CACHE_KEY = "claudeCode.detectedCCVersion";
16880
+ var CC_VERSION_CLI_KEY = "claudeCode.detectedCCVersionCli";
16845
16881
  var GLOBAL_COOLDOWN_MS = 5e3;
16846
16882
  var HOOK_COOLDOWN_MS = 15e3;
16847
16883
  var ACTIVE_LOCK_TTL_MS = 6e4;
@@ -16852,14 +16888,35 @@ var MAX_ENTRIES_PER_INVOCATION = 50;
16852
16888
  var COOLDOWN_KEY = "lastHookRunAt";
16853
16889
  var ACTIVE_KEY = "hookActiveAt";
16854
16890
  var HookDataSchema = z33.object({
16855
- session_id: z33.string(),
16856
- transcript_path: z33.string(),
16857
- cwd: z33.string(),
16891
+ session_id: z33.string().nullish(),
16892
+ transcript_path: z33.string().nullish(),
16893
+ cwd: z33.string().nullish(),
16858
16894
  hook_event_name: z33.string(),
16859
16895
  tool_name: z33.string(),
16860
16896
  tool_input: z33.unknown(),
16861
16897
  tool_response: z33.unknown()
16862
16898
  });
16899
+ var execFileAsync = promisify(execFile);
16900
+ async function detectClaudeCodeVersion() {
16901
+ const cachedCliVersion = configStore.get(CC_VERSION_CLI_KEY);
16902
+ if (cachedCliVersion === packageJson.version) {
16903
+ return configStore.get(CC_VERSION_CACHE_KEY);
16904
+ }
16905
+ try {
16906
+ const { stdout: stdout2 } = await execFileAsync("claude", ["--version"], {
16907
+ timeout: 3e3,
16908
+ encoding: "utf-8"
16909
+ });
16910
+ const version = stdout2.trim().split(/\s/)[0] || stdout2.trim();
16911
+ configStore.set(CC_VERSION_CACHE_KEY, version);
16912
+ configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
16913
+ return version;
16914
+ } catch {
16915
+ configStore.set(CC_VERSION_CACHE_KEY, void 0);
16916
+ configStore.set(CC_VERSION_CLI_KEY, packageJson.version);
16917
+ return void 0;
16918
+ }
16919
+ }
16863
16920
  var STDIN_TIMEOUT_MS = 1e4;
16864
16921
  async function readStdinData() {
16865
16922
  hookLog.debug("Reading stdin data");
@@ -16911,6 +16968,24 @@ async function readStdinData() {
16911
16968
  function validateHookData(data) {
16912
16969
  return HookDataSchema.parse(data);
16913
16970
  }
16971
+ async function extractSessionIdFromTranscript(transcriptPath) {
16972
+ try {
16973
+ const fh = await open4(transcriptPath, "r");
16974
+ try {
16975
+ const buf = Buffer.alloc(4096);
16976
+ const { bytesRead } = await fh.read(buf, 0, 4096, 0);
16977
+ const chunk = buf.toString("utf-8", 0, bytesRead);
16978
+ const firstLine = chunk.split("\n").find((l) => l.trim().length > 0);
16979
+ if (!firstLine) return null;
16980
+ const entry = JSON.parse(firstLine);
16981
+ return entry.sessionId ?? null;
16982
+ } finally {
16983
+ await fh.close();
16984
+ }
16985
+ } catch {
16986
+ return null;
16987
+ }
16988
+ }
16914
16989
  function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
16915
16990
  const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
16916
16991
  const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
@@ -17201,9 +17276,94 @@ async function processAndUploadTranscriptEntries() {
17201
17276
  hookLog.info("Auto-upgraded hook matcher to reduce CPU usage");
17202
17277
  }
17203
17278
  }
17279
+ try {
17280
+ const ccVersion = await detectClaudeCodeVersion();
17281
+ setClaudeCodeVersion(ccVersion);
17282
+ } catch {
17283
+ }
17204
17284
  const rawData = await readStdinData();
17205
- const hookData = validateHookData(rawData);
17206
- const sessionStore = createSessionConfigStore(hookData.session_id);
17285
+ const rawObj = rawData;
17286
+ const hookData = (() => {
17287
+ try {
17288
+ return validateHookData(rawData);
17289
+ } catch (err) {
17290
+ hookLog.error(
17291
+ {
17292
+ data: {
17293
+ hook_event_name: rawObj?.["hook_event_name"],
17294
+ tool_name: rawObj?.["tool_name"],
17295
+ session_id: rawObj?.["session_id"],
17296
+ cwd: rawObj?.["cwd"],
17297
+ keys: rawObj ? Object.keys(rawObj) : []
17298
+ }
17299
+ },
17300
+ `Hook validation failed: ${err.message?.slice(0, 200)}`
17301
+ );
17302
+ throw err;
17303
+ }
17304
+ })();
17305
+ if (!hookData.transcript_path) {
17306
+ hookLog.warn(
17307
+ {
17308
+ data: {
17309
+ hook_event_name: hookData.hook_event_name,
17310
+ tool_name: hookData.tool_name,
17311
+ session_id: hookData.session_id,
17312
+ cwd: hookData.cwd
17313
+ }
17314
+ },
17315
+ "Missing transcript_path \u2014 cannot process hook"
17316
+ );
17317
+ return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17318
+ }
17319
+ let sessionId = hookData.session_id;
17320
+ if (!sessionId) {
17321
+ sessionId = await extractSessionIdFromTranscript(hookData.transcript_path);
17322
+ if (sessionId) {
17323
+ hookLog.warn(
17324
+ {
17325
+ data: {
17326
+ hook_event_name: hookData.hook_event_name,
17327
+ tool_name: hookData.tool_name,
17328
+ cwd: hookData.cwd,
17329
+ extractedSessionId: sessionId
17330
+ }
17331
+ },
17332
+ "Missing session_id in hook data \u2014 extracted from transcript"
17333
+ );
17334
+ } else {
17335
+ hookLog.warn(
17336
+ {
17337
+ data: {
17338
+ hook_event_name: hookData.hook_event_name,
17339
+ tool_name: hookData.tool_name,
17340
+ transcript_path: hookData.transcript_path
17341
+ }
17342
+ },
17343
+ "Missing session_id and could not extract from transcript \u2014 cannot process hook"
17344
+ );
17345
+ return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17346
+ }
17347
+ }
17348
+ if (!hookData.cwd) {
17349
+ hookLog.warn(
17350
+ {
17351
+ data: {
17352
+ hook_event_name: hookData.hook_event_name,
17353
+ tool_name: hookData.tool_name,
17354
+ session_id: sessionId
17355
+ }
17356
+ },
17357
+ "Missing cwd in hook data \u2014 scoped logging and repo URL detection disabled"
17358
+ );
17359
+ }
17360
+ const resolvedHookData = {
17361
+ ...hookData,
17362
+ session_id: sessionId,
17363
+ transcript_path: hookData.transcript_path,
17364
+ cwd: hookData.cwd ?? void 0
17365
+ };
17366
+ const sessionStore = createSessionConfigStore(resolvedHookData.session_id);
17207
17367
  await cleanupStaleSessions(sessionStore);
17208
17368
  const now = Date.now();
17209
17369
  const lastRunAt = sessionStore.get(COOLDOWN_KEY);
@@ -17218,7 +17378,7 @@ async function processAndUploadTranscriptEntries() {
17218
17378
  {
17219
17379
  data: {
17220
17380
  activeDurationMs: activeDuration,
17221
- sessionId: hookData.session_id
17381
+ sessionId: resolvedHookData.session_id
17222
17382
  }
17223
17383
  },
17224
17384
  "Hook still active \u2014 possible slow upload or hung process"
@@ -17228,20 +17388,21 @@ async function processAndUploadTranscriptEntries() {
17228
17388
  }
17229
17389
  sessionStore.set(ACTIVE_KEY, now);
17230
17390
  sessionStore.set(COOLDOWN_KEY, now);
17231
- const log2 = createScopedHookLog(hookData.cwd);
17391
+ const log2 = createScopedHookLog(resolvedHookData.cwd ?? process.cwd());
17232
17392
  log2.info(
17233
17393
  {
17234
17394
  data: {
17235
- sessionId: hookData.session_id,
17236
- toolName: hookData.tool_name,
17237
- hookEvent: hookData.hook_event_name,
17238
- cwd: hookData.cwd
17395
+ sessionId: resolvedHookData.session_id,
17396
+ toolName: resolvedHookData.tool_name,
17397
+ hookEvent: resolvedHookData.hook_event_name,
17398
+ cwd: resolvedHookData.cwd,
17399
+ claudeCodeVersion: getClaudeCodeVersion()
17239
17400
  }
17240
17401
  },
17241
17402
  "Hook data validated"
17242
17403
  );
17243
17404
  try {
17244
- return await processTranscript(hookData, sessionStore, log2);
17405
+ return await processTranscript(resolvedHookData, sessionStore, log2);
17245
17406
  } finally {
17246
17407
  sessionStore.delete(ACTIVE_KEY);
17247
17408
  log2.flushLogs();
@@ -17330,6 +17491,9 @@ async function processTranscript(hookData, sessionStore, log2) {
17330
17491
  rawEntry["message"] = { model: lastSeenModel };
17331
17492
  }
17332
17493
  }
17494
+ if (!rawEntry["sessionId"]) {
17495
+ rawEntry["sessionId"] = hookData.session_id;
17496
+ }
17333
17497
  return {
17334
17498
  platform: "CLAUDE_CODE" /* ClaudeCode */,
17335
17499
  recordId: _recordId,
@@ -17371,7 +17535,8 @@ async function processTranscript(hookData, sessionStore, log2) {
17371
17535
  sessionStore.set(cursorKey, cursor);
17372
17536
  log2.heartbeat("Upload ok", {
17373
17537
  entriesUploaded: entries.length,
17374
- entriesSkipped: filteredOut
17538
+ entriesSkipped: filteredOut,
17539
+ claudeCodeVersion: getClaudeCodeVersion()
17375
17540
  });
17376
17541
  return {
17377
17542
  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.65",
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",