hackmyagent 0.11.3 → 0.11.4

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 +1 @@
1
- {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,UAAU,EAA0C,MAAM,kBAAkB,CAAC;AAkF3F,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8HD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAiB;IAEhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAiBlC;IAEF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAMvB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;YAmSvC,cAAc;IAwE5B;;OAEG;YACW,iBAAiB;IA+F/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;YAeV,uBAAuB;YAmGvB,aAAa;YAgDb,cAAc;YA+Fd,oBAAoB;YAwDpB,gBAAgB;YA0IhB,oBAAoB;YAgFpB,gBAAgB;YA2IhB,mBAAmB;YA4EnB,iBAAiB;YAyCjB,iBAAiB;YA+DjB,wBAAwB;YA0FxB,wBAAwB;YAmExB,wBAAwB;YAqHxB,oBAAoB;YA+GpB,uBAAuB;YAwIvB,iBAAiB;YA8GjB,oBAAoB;YAsHpB,mBAAmB;YAiGnB,gBAAgB;YAmIhB,oBAAoB;YAoIpB,gBAAgB;YAyHhB,qBAAqB;YA+GrB,eAAe;IAiI7B;;OAEG;YACW,mBAAmB;IA8GjC;;OAEG;YACW,oBAAoB;IAiKlC;;OAEG;YACW,iBAAiB;IA4I/B;;OAEG;YACW,oBAAoB;IAwIlC;;OAEG;YACW,eAAe;IAqJ7B;;OAEG;YACW,eAAe;IAuI7B;;OAEG;YACW,eAAe;IAyG7B;;OAEG;YACW,mBAAmB;IAmHjC,OAAO,CAAC,cAAc;IAsBtB;;OAEG;YACW,YAAY;IAkD1B;;OAEG;IACG,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6DhD;;;OAGG;YACW,cAAc;IAgD5B;;OAEG;YACW,mBAAmB;IAycjC;;;OAGG;YACW,kBAAkB;IAgDhC;;OAEG;YACW,sBAAsB;IA2LpC;;OAEG;YACW,sBAAsB;IA+BpC;;OAEG;YACW,oBAAoB;IAqVlC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;YACW,iBAAiB;IA8D/B;;OAEG;YACW,mBAAmB;IA6VjC;;OAEG;YACW,wBAAwB;IA4OtC;;OAEG;YACW,gBAAgB;IA6J9B;;;OAGG;YACW,eAAe;IAoD7B;;;OAGG;YACW,aAAa;IAwC3B;;;OAGG;YACW,oBAAoB;IA+JlC;;;OAGG;YACW,iBAAiB;IA6H/B;;;OAGG;YACW,kBAAkB;IA+EhC;;;OAGG;YACW,aAAa;IAuF3B;;OAEG;YACW,gBAAgB;IA+D9B;;;;OAIG;YACW,yBAAyB;CA4NxC"}
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,UAAU,EAA0C,MAAM,kBAAkB,CAAC;AAkF3F,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAoID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAiB;IAEhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAiBlC;IAEF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAMvB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;YAmSvC,cAAc;IAwE5B;;OAEG;YACW,iBAAiB;IA+F/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;YAeV,uBAAuB;YAmGvB,aAAa;YAgDb,cAAc;YA+Fd,oBAAoB;YAwDpB,gBAAgB;YA0IhB,oBAAoB;YAgFpB,gBAAgB;YA2IhB,mBAAmB;YA4EnB,iBAAiB;YAyCjB,iBAAiB;YA+DjB,wBAAwB;YA0FxB,wBAAwB;YAmExB,wBAAwB;YAqHxB,oBAAoB;YA+GpB,uBAAuB;YAwIvB,iBAAiB;YA8GjB,oBAAoB;YAsHpB,mBAAmB;YAiGnB,gBAAgB;YAmIhB,oBAAoB;YAoIpB,gBAAgB;YAyHhB,qBAAqB;YA+GrB,eAAe;IAiI7B;;OAEG;YACW,mBAAmB;IA8GjC;;OAEG;YACW,oBAAoB;IAiKlC;;OAEG;YACW,iBAAiB;IA4I/B;;OAEG;YACW,oBAAoB;IAwIlC;;OAEG;YACW,eAAe;IAqJ7B;;OAEG;YACW,eAAe;IAuI7B;;OAEG;YACW,eAAe;IAyG7B;;OAEG;YACW,mBAAmB;IAmHjC,OAAO,CAAC,cAAc;IAsBtB;;OAEG;YACW,YAAY;IAkD1B;;OAEG;IACG,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6DhD;;;OAGG;YACW,cAAc;IAgD5B;;OAEG;YACW,mBAAmB;IAycjC;;;OAGG;YACW,kBAAkB;IAgDhC;;OAEG;YACW,sBAAsB;IA2LpC;;OAEG;YACW,sBAAsB;IA+BpC;;OAEG;YACW,oBAAoB;IAqVlC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;YACW,iBAAiB;IA8D/B;;OAEG;YACW,mBAAmB;IA6VjC;;OAEG;YACW,wBAAwB;IA4OtC;;OAEG;YACW,gBAAgB;IA6J9B;;;OAGG;YACW,eAAe;IAoD7B;;;OAGG;YACW,aAAa;IAwC3B;;;OAGG;YACW,oBAAoB;IA+JlC;;;OAGG;YACW,iBAAiB;IA6H/B;;;OAGG;YACW,kBAAkB;IA+EhC;;;OAGG;YACW,aAAa;IAuF3B;;OAEG;YACW,gBAAgB;IA+D9B;;;;OAIG;YACW,yBAAyB;CAoWxC"}
@@ -218,6 +218,11 @@ const SEVERITY_WEIGHTS = {
218
218
  };
219
219
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB max file size to prevent memory exhaustion
220
220
  const MAX_LINE_LENGTH = 10000; // 10KB max line length for regex safety
221
+ /** Shell-escape a string for safe interpolation into advisory fix commands. */
222
+ function shellEscape(s) {
223
+ // Wrap in single quotes and escape embedded single quotes: ' -> '\''
224
+ return "'" + s.replace(/'/g, "'\\''") + "'";
225
+ }
221
226
  class HardeningScanner {
222
227
  constructor() {
223
228
  this.cliName = 'hackmyagent';
@@ -3756,7 +3761,7 @@ dist/
3756
3761
  await fs.access(backupBaseDir);
3757
3762
  }
3758
3763
  catch {
3759
- throw new Error('No backup found. Run hackmyagent harden --fix <dir> first to create a backup.');
3764
+ throw new Error('No backup found. Run hackmyagent secure --fix <dir> first to create a backup.');
3760
3765
  }
3761
3766
  // Find the most recent backup
3762
3767
  const backups = await fs.readdir(backupBaseDir);
@@ -3765,7 +3770,7 @@ dist/
3765
3770
  .sort()
3766
3771
  .reverse();
3767
3772
  if (sortedBackups.length === 0) {
3768
- throw new Error('No backup found. Run hackmyagent harden --fix <dir> first to create a backup.');
3773
+ throw new Error('No backup found. Run hackmyagent secure --fix <dir> first to create a backup.');
3769
3774
  }
3770
3775
  const latestBackup = sortedBackups[0];
3771
3776
  const backupDir = path.join(backupBaseDir, latestBackup);
@@ -4883,8 +4888,8 @@ dist/
4883
4888
  if (!this.isPathWithinDirectory(fullPath, targetDir)) {
4884
4889
  continue;
4885
4890
  }
4886
- // Skip node_modules and .git directories
4887
- if (entryName === 'node_modules' || entryName === '.git') {
4891
+ // Skip node_modules, .git, and backup directories
4892
+ if (entryName === 'node_modules' || entryName === '.git' || entryName === '.hackmyagent-backup') {
4888
4893
  continue;
4889
4894
  }
4890
4895
  let stat;
@@ -6224,7 +6229,9 @@ dist/
6224
6229
  */
6225
6230
  async checkUnicodeSteganography(targetDir, _autoFix) {
6226
6231
  const findings = [];
6227
- const sourceFiles = await this.findSourceFiles(targetDir, targetDir);
6232
+ // Scan expanded file types beyond JS/TS (configs, docs, and Python are attack surfaces too)
6233
+ const stegoExtensions = ['.ts', '.js', '.mjs', '.cjs', '.tsx', '.jsx', '.py', '.md', '.txt', '.yaml', '.yml', '.json', '.toml'];
6234
+ const sourceFiles = await this.walkDirectory(targetDir, stegoExtensions);
6228
6235
  for (const filePath of sourceFiles) {
6229
6236
  const relativePath = path.relative(targetDir, filePath);
6230
6237
  let rawBuffer;
@@ -6238,12 +6245,22 @@ dist/
6238
6245
  if (rawBuffer.length > MAX_FILE_SIZE)
6239
6246
  continue;
6240
6247
  // UNICODE-STEGO-001: Invisible Codepoint Detection
6241
- // Scan for variation selectors U+FE00-FE0F (UTF-8: EF B8 80-8F)
6242
- // and tag characters U+E0100-E01EF (UTF-8: F3 A0 84 80 - F3 A0 87 AF)
6248
+ // Scan for:
6249
+ // - Variation selectors U+FE00-FE0F (UTF-8: EF B8 80-8F)
6250
+ // - Tag characters U+E0100-E01EF (UTF-8: F3 A0 84 80 - F3 A0 87 AF)
6251
+ // - Zero-width chars: U+200B (E2 80 8B), U+200C (E2 80 8C), U+200D (E2 80 8D)
6252
+ // - Mid-file BOM: U+FEFF (EF BB BF) -- skip offset 0
6253
+ // - Bidi overrides: U+202A-202E (E2 80 AA-AE), U+2066-2069 (E2 81 A6-A9)
6243
6254
  let hasVariationSelectors = false;
6244
6255
  let variationSelectorLine = 1;
6245
6256
  let hasTagCharsIn001 = false;
6246
6257
  let tagCharLine001 = 1;
6258
+ let hasZeroWidth = false;
6259
+ let zeroWidthLine = 1;
6260
+ let hasMidFileBom = false;
6261
+ let midFileBomLine = 1;
6262
+ let hasBidiOverride = false;
6263
+ let bidiOverrideLine = 1;
6247
6264
  let currentLine = 1;
6248
6265
  for (let i = 0; i < rawBuffer.length; i++) {
6249
6266
  if (rawBuffer[i] === 0x0A) {
@@ -6272,25 +6289,86 @@ dist/
6272
6289
  tagCharLine001 = currentLine;
6273
6290
  }
6274
6291
  }
6292
+ // Zero-width chars: U+200B/200C/200D = E2 80 8B/8C/8D
6293
+ if (rawBuffer[i] === 0xE2 &&
6294
+ i + 2 < rawBuffer.length &&
6295
+ rawBuffer[i + 1] === 0x80 &&
6296
+ rawBuffer[i + 2] >= 0x8B &&
6297
+ rawBuffer[i + 2] <= 0x8D) {
6298
+ if (!hasZeroWidth) {
6299
+ hasZeroWidth = true;
6300
+ zeroWidthLine = currentLine;
6301
+ }
6302
+ }
6303
+ // Mid-file BOM: U+FEFF = EF BB BF (skip if at offset 0)
6304
+ if (i > 0 &&
6305
+ rawBuffer[i] === 0xEF &&
6306
+ i + 2 < rawBuffer.length &&
6307
+ rawBuffer[i + 1] === 0xBB &&
6308
+ rawBuffer[i + 2] === 0xBF) {
6309
+ if (!hasMidFileBom) {
6310
+ hasMidFileBom = true;
6311
+ midFileBomLine = currentLine;
6312
+ }
6313
+ }
6314
+ // Bidi overrides: U+202A-202E = E2 80 AA-AE
6315
+ if (rawBuffer[i] === 0xE2 &&
6316
+ i + 2 < rawBuffer.length &&
6317
+ rawBuffer[i + 1] === 0x80 &&
6318
+ rawBuffer[i + 2] >= 0xAA &&
6319
+ rawBuffer[i + 2] <= 0xAE) {
6320
+ if (!hasBidiOverride) {
6321
+ hasBidiOverride = true;
6322
+ bidiOverrideLine = currentLine;
6323
+ }
6324
+ }
6325
+ // Bidi isolates: U+2066-2069 = E2 81 A6-A9
6326
+ if (rawBuffer[i] === 0xE2 &&
6327
+ i + 2 < rawBuffer.length &&
6328
+ rawBuffer[i + 1] === 0x81 &&
6329
+ rawBuffer[i + 2] >= 0xA6 &&
6330
+ rawBuffer[i + 2] <= 0xA9) {
6331
+ if (!hasBidiOverride) {
6332
+ hasBidiOverride = true;
6333
+ bidiOverrideLine = currentLine;
6334
+ }
6335
+ }
6275
6336
  }
6276
- if (hasVariationSelectors || hasTagCharsIn001) {
6337
+ // Bidi and variation/tag chars are critical; zero-width-only is high
6338
+ const hasCriticalInvisible = hasVariationSelectors || hasTagCharsIn001 || hasBidiOverride;
6339
+ const hasAnyInvisible = hasCriticalInvisible || hasZeroWidth || hasMidFileBom;
6340
+ if (hasAnyInvisible) {
6277
6341
  const detectedTypes = [];
6278
6342
  if (hasVariationSelectors)
6279
6343
  detectedTypes.push('variation selectors (U+FE00-FE0F)');
6280
6344
  if (hasTagCharsIn001)
6281
6345
  detectedTypes.push('tag characters (U+E0100-E01EF)');
6346
+ if (hasZeroWidth)
6347
+ detectedTypes.push('zero-width characters (U+200B-200D)');
6348
+ if (hasMidFileBom)
6349
+ detectedTypes.push('mid-file BOM (U+FEFF)');
6350
+ if (hasBidiOverride)
6351
+ detectedTypes.push('bidi overrides (U+202A-202E, U+2066-2069)');
6352
+ // Determine first line hit for reporting
6353
+ const firstLine = Math.min(...[
6354
+ hasVariationSelectors ? variationSelectorLine : Infinity,
6355
+ hasTagCharsIn001 ? tagCharLine001 : Infinity,
6356
+ hasZeroWidth ? zeroWidthLine : Infinity,
6357
+ hasMidFileBom ? midFileBomLine : Infinity,
6358
+ hasBidiOverride ? bidiOverrideLine : Infinity,
6359
+ ]);
6282
6360
  findings.push({
6283
6361
  checkId: 'UNICODE-STEGO-001',
6284
6362
  name: 'Invisible Unicode Codepoints Detected',
6285
6363
  description: 'Source file contains invisible Unicode codepoints that can hide malicious payloads (GlassWorm attack vector)',
6286
6364
  category: 'unicode-stego',
6287
- severity: 'critical',
6365
+ severity: hasCriticalInvisible ? 'critical' : 'high',
6288
6366
  passed: false,
6289
6367
  message: `Found ${detectedTypes.join(' and ')} in ${relativePath}`,
6290
6368
  file: relativePath,
6291
- line: hasVariationSelectors ? variationSelectorLine : tagCharLine001,
6369
+ line: firstLine,
6292
6370
  fixable: false,
6293
- fix: 'Inspect the file with a hex editor (e.g., xxd) to identify and remove invisible Unicode codepoints. Run: xxd ' + relativePath + ' | grep -E "fe0[0-9a-f]|f3a0"',
6371
+ fix: 'Inspect the file with a hex editor (e.g., xxd) to identify and remove invisible Unicode codepoints. Run: xxd ' + shellEscape(relativePath) + ' | grep -iE "e280[8-9a-e]|efbb|efb8|f3a0"',
6294
6372
  });
6295
6373
  }
6296
6374
  // UNICODE-STEGO-002: GlassWorm Decoder Pattern
@@ -6380,7 +6458,7 @@ dist/
6380
6458
  file: relativePath,
6381
6459
  line: evalLine,
6382
6460
  fixable: false,
6383
- fix: 'Remove the eval/Function call and inspect the string argument with a hex editor. The string likely contains invisible Unicode characters encoding a malicious payload. Run: node -e "const fs=require(\'fs\'); const s=fs.readFileSync(\'' + relativePath + '\',\'utf8\'); console.log([...s].filter(c=>c.codePointAt(0)>0x200).map(c=>c.codePointAt(0).toString(16)))"',
6461
+ fix: 'Remove the eval/Function call and inspect the string argument with a hex editor. The string likely contains invisible Unicode characters encoding a malicious payload. Run: node -e "const fs=require(\'fs\'); const s=fs.readFileSync(' + JSON.stringify(relativePath) + ',\'utf8\'); console.log([...s].filter(c=>c.codePointAt(0)>0x200).map(c=>c.codePointAt(0).toString(16)))"',
6384
6462
  });
6385
6463
  break; // One finding per file
6386
6464
  }
@@ -6422,10 +6500,61 @@ dist/
6422
6500
  file: relativePath,
6423
6501
  line: tagBlockLine,
6424
6502
  fixable: false,
6425
- fix: 'Inspect the file with a hex editor to identify tag block characters (byte sequence starting with F3 A0). These characters are invisible and have no legitimate use in source code. Run: xxd ' + relativePath + ' | grep "f3a0"',
6503
+ fix: 'Inspect the file with a hex editor to identify tag block characters (byte sequence starting with F3 A0). These characters are invisible and have no legitimate use in source code. Run: xxd ' + shellEscape(relativePath) + ' | grep "f3a0"',
6426
6504
  });
6427
6505
  }
6428
6506
  }
6507
+ // UNICODE-STEGO-005: Homoglyph Confusable Detection
6508
+ // Detect Cyrillic/Greek characters that look identical to Latin but have different codepoints.
6509
+ // These can be used to bypass code review and hide malicious identifiers.
6510
+ const homoglyphCodepoints = new Set([
6511
+ // Cyrillic uppercase that look like Latin: A, B, C, E, H, K, M, O, P, T, X
6512
+ 0x0410, 0x0412, 0x0421, 0x0415, 0x041D, 0x041A, 0x041C, 0x041E, 0x0420, 0x0422, 0x0425,
6513
+ // Cyrillic lowercase that look like Latin: a, e, o, p, c, x
6514
+ 0x0430, 0x0435, 0x043E, 0x0440, 0x0441, 0x0445,
6515
+ // Fullwidth Latin (U+FF21-FF3A, U+FF41-FF5A) -- spot-check common ones
6516
+ 0xFF21, 0xFF22, 0xFF41, 0xFF42,
6517
+ ]);
6518
+ let homoglyphFound = false;
6519
+ let homoglyphLine = 1;
6520
+ let homoglyphChar = '';
6521
+ const contentForHomoglyph = content || rawBuffer.toString('utf-8');
6522
+ const homoglyphLines = contentForHomoglyph.split('\n');
6523
+ for (let lineIdx = 0; lineIdx < homoglyphLines.length; lineIdx++) {
6524
+ const line = homoglyphLines[lineIdx];
6525
+ if (line.length > MAX_LINE_LENGTH)
6526
+ continue;
6527
+ // Skip comment lines
6528
+ const trimmed = line.trimStart();
6529
+ if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
6530
+ continue;
6531
+ for (const ch of line) {
6532
+ const cp = ch.codePointAt(0);
6533
+ if (homoglyphCodepoints.has(cp)) {
6534
+ homoglyphFound = true;
6535
+ homoglyphLine = lineIdx + 1;
6536
+ homoglyphChar = `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;
6537
+ break;
6538
+ }
6539
+ }
6540
+ if (homoglyphFound)
6541
+ break;
6542
+ }
6543
+ if (homoglyphFound) {
6544
+ findings.push({
6545
+ checkId: 'UNICODE-STEGO-005',
6546
+ name: 'Homoglyph Confusable Characters Detected',
6547
+ description: 'Source file contains characters from non-Latin scripts (Cyrillic, Greek, Fullwidth) that visually resemble Latin letters. These can be used to create identifiers that look identical in code review but behave differently at runtime.',
6548
+ category: 'unicode-stego',
6549
+ severity: 'high',
6550
+ passed: false,
6551
+ message: `Found homoglyph confusable character (${homoglyphChar}) in ${relativePath} at line ${homoglyphLine}`,
6552
+ file: relativePath,
6553
+ line: homoglyphLine,
6554
+ fixable: false,
6555
+ fix: 'Inspect the file for characters that look like Latin letters but are actually Cyrillic, Greek, or Fullwidth. Replace them with their ASCII equivalents. Run: node -e "const fs=require(\'fs\'); [...fs.readFileSync(' + JSON.stringify(relativePath) + ',\'utf8\')].forEach((c,i)=>{const cp=c.codePointAt(0); if(cp>0x7F && cp<0xFFFF) console.log(i, cp.toString(16), c)})"',
6556
+ });
6557
+ }
6429
6558
  }
6430
6559
  return findings;
6431
6560
  }