hackmyagent 0.15.7 → 0.16.1

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.
Files changed (47) hide show
  1. package/dist/.integrity-manifest.json +1 -1
  2. package/dist/arp/intelligence/nanomind-l1.d.ts +30 -0
  3. package/dist/arp/intelligence/nanomind-l1.d.ts.map +1 -1
  4. package/dist/arp/intelligence/nanomind-l1.js +115 -0
  5. package/dist/arp/intelligence/nanomind-l1.js.map +1 -1
  6. package/dist/cli.js +452 -244
  7. package/dist/cli.js.map +1 -1
  8. package/dist/hardening/scanner.d.ts.map +1 -1
  9. package/dist/hardening/scanner.js +125 -4
  10. package/dist/hardening/scanner.js.map +1 -1
  11. package/dist/hardening/taxonomy.d.ts +2 -0
  12. package/dist/hardening/taxonomy.d.ts.map +1 -1
  13. package/dist/hardening/taxonomy.js +5 -0
  14. package/dist/hardening/taxonomy.js.map +1 -1
  15. package/dist/nanomind-core/analyzers/stego-analyzer.d.ts +30 -0
  16. package/dist/nanomind-core/analyzers/stego-analyzer.d.ts.map +1 -0
  17. package/dist/nanomind-core/analyzers/stego-analyzer.js +533 -0
  18. package/dist/nanomind-core/analyzers/stego-analyzer.js.map +1 -0
  19. package/dist/nanomind-core/daemon-lifecycle.d.ts +28 -0
  20. package/dist/nanomind-core/daemon-lifecycle.d.ts.map +1 -0
  21. package/dist/nanomind-core/daemon-lifecycle.js +142 -0
  22. package/dist/nanomind-core/daemon-lifecycle.js.map +1 -0
  23. package/dist/nanomind-core/inference/tme-classifier.d.ts +3 -2
  24. package/dist/nanomind-core/inference/tme-classifier.d.ts.map +1 -1
  25. package/dist/nanomind-core/inference/tme-classifier.js +26 -16
  26. package/dist/nanomind-core/inference/tme-classifier.js.map +1 -1
  27. package/dist/nanomind-core/orchestrate.d.ts.map +1 -1
  28. package/dist/nanomind-core/orchestrate.js +11 -1
  29. package/dist/nanomind-core/orchestrate.js.map +1 -1
  30. package/dist/nanomind-core/scanner-bridge.d.ts.map +1 -1
  31. package/dist/nanomind-core/scanner-bridge.js +6 -0
  32. package/dist/nanomind-core/scanner-bridge.js.map +1 -1
  33. package/dist/plugins/credvault.d.ts.map +1 -1
  34. package/dist/plugins/credvault.js +25 -0
  35. package/dist/plugins/credvault.js.map +1 -1
  36. package/dist/semantic/nanomind-enhancer.d.ts.map +1 -1
  37. package/dist/semantic/nanomind-enhancer.js +206 -0
  38. package/dist/semantic/nanomind-enhancer.js.map +1 -1
  39. package/dist/telemetry/nanomind-feedback.d.ts +43 -0
  40. package/dist/telemetry/nanomind-feedback.d.ts.map +1 -0
  41. package/dist/telemetry/nanomind-feedback.js +104 -0
  42. package/dist/telemetry/nanomind-feedback.js.map +1 -0
  43. package/dist/telemetry/nanomind-telemetry.d.ts +48 -0
  44. package/dist/telemetry/nanomind-telemetry.d.ts.map +1 -0
  45. package/dist/telemetry/nanomind-telemetry.js +123 -0
  46. package/dist/telemetry/nanomind-telemetry.js.map +1 -0
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"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"}
@@ -246,6 +246,101 @@ function shellEscape(s) {
246
246
  // Wrap in single quotes and escape embedded single quotes: ' -> '\''
247
247
  return "'" + s.replace(/'/g, "'\\''") + "'";
248
248
  }
249
+ /**
250
+ * Check if a variation selector at position i in rawBuffer is a legitimate
251
+ * emoji presentation selector (U+FE0F following an emoji base character).
252
+ *
253
+ * Emoji base characters that commonly precede FE0F:
254
+ * - Keycap digits/symbols: 0-9, #, * (encoded as single ASCII bytes)
255
+ * - BMP symbols: U+2600-27BF range (encoded as 3-byte UTF-8: E2 XX XX or E2 XX XX)
256
+ * - SMP emoji: U+1F300-1FAFF (encoded as 4-byte UTF-8: F0 9F XX XX)
257
+ */
258
+ function isEmojiVariationSelector(buf, vsStart) {
259
+ // Walk backward to find the preceding character
260
+ // The variation selector is at vsStart (3 bytes: EF B8 8F)
261
+ // We need to check what character precedes it
262
+ if (vsStart === 0)
263
+ return false;
264
+ // Check for 4-byte SMP emoji before (F0 9F XX XX) — most common case
265
+ if (vsStart >= 4) {
266
+ const b0 = buf[vsStart - 4];
267
+ const b1 = buf[vsStart - 3];
268
+ if (b0 === 0xF0 && b1 === 0x9F)
269
+ return true; // U+1F000-1FFFF (emoji range)
270
+ }
271
+ // Check for 3-byte BMP symbol before (E2 XX XX) — symbols like warning, gear, etc.
272
+ if (vsStart >= 3) {
273
+ const b0 = buf[vsStart - 3];
274
+ const b1 = buf[vsStart - 2];
275
+ if (b0 === 0xE2) {
276
+ // U+2600-27BF: Misc Symbols, Dingbats (E2 98 80 through E2 9E BF)
277
+ if (b1 >= 0x98 && b1 <= 0x9E)
278
+ return true;
279
+ // U+2300-23FF: Misc Technical (E2 8C 80 through E2 8F BF) — includes hourglass, etc.
280
+ if (b1 >= 0x8C && b1 <= 0x8F)
281
+ return true;
282
+ }
283
+ // U+2700-27BF also encoded as E2 9C XX - E2 9E XX
284
+ if (b0 === 0xE2 && b1 >= 0x9C && b1 <= 0x9E)
285
+ return true;
286
+ }
287
+ // Check for 1-byte ASCII keycap base: #, *, 0-9
288
+ if (vsStart >= 1) {
289
+ const prev = buf[vsStart - 1];
290
+ if (prev === 0x23 || prev === 0x2A)
291
+ return true; // # or *
292
+ if (prev >= 0x30 && prev <= 0x39)
293
+ return true; // 0-9
294
+ }
295
+ return false;
296
+ }
297
+ /**
298
+ * Check if a Cyrillic character at position ci in chars[] is in a Cyrillic
299
+ * text context (legitimate i18n) rather than mixed into a Latin word (attack).
300
+ *
301
+ * Looks at a window of nearby characters. If the neighborhood contains
302
+ * mostly Cyrillic or other non-Latin chars, it's i18n. If surrounded by
303
+ * Latin chars, it's a homoglyph attack.
304
+ */
305
+ function isCyrillicInCyrillicContext(chars, ci) {
306
+ // Look at a window of 10 chars in each direction
307
+ const windowSize = 10;
308
+ const start = Math.max(0, ci - windowSize);
309
+ const end = Math.min(chars.length, ci + windowSize + 1);
310
+ let latinCount = 0;
311
+ let cyrillicCount = 0;
312
+ for (let j = start; j < end; j++) {
313
+ if (j === ci)
314
+ continue;
315
+ const cp = chars[j].codePointAt(0);
316
+ // Latin letter
317
+ if ((cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A)) {
318
+ latinCount++;
319
+ }
320
+ // Any Cyrillic (U+0400-052F)
321
+ if (cp >= 0x0400 && cp <= 0x052F) {
322
+ cyrillicCount++;
323
+ }
324
+ }
325
+ // If there are at least 3 other Cyrillic chars nearby, this is i18n text
326
+ // (translations, i18n badges, etc. always have multiple Cyrillic chars together)
327
+ if (cyrillicCount >= 3)
328
+ return true;
329
+ // If the immediate neighbors are both Latin, this is a homoglyph attack
330
+ const prevLatin = ci > 0 && (() => {
331
+ const cp = chars[ci - 1].codePointAt(0);
332
+ return (cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A);
333
+ })();
334
+ const nextLatin = ci < chars.length - 1 && (() => {
335
+ const cp = chars[ci + 1].codePointAt(0);
336
+ return (cp >= 0x41 && cp <= 0x5A) || (cp >= 0x61 && cp <= 0x7A);
337
+ })();
338
+ if (prevLatin && nextLatin)
339
+ return false; // Sandwiched in Latin = attack
340
+ // Ambiguous case: not enough context. If there are ANY other Cyrillic
341
+ // chars nearby, give benefit of the doubt (i18n).
342
+ return cyrillicCount > 0;
343
+ }
249
344
  class HardeningScanner {
250
345
  constructor() {
251
346
  this.cliName = 'hackmyagent';
@@ -6872,13 +6967,19 @@ dist/
6872
6967
  currentLine++;
6873
6968
  continue;
6874
6969
  }
6875
- // Variation selectors: EF B8 80-8F
6970
+ // Variation selectors: EF B8 80-8F (U+FE00-FE0F)
6971
+ // Skip U+FE0F (EF B8 8F) when preceded by an emoji base character,
6972
+ // as it's the standard emoji presentation selector (not steganography).
6876
6973
  if (rawBuffer[i] === 0xEF &&
6877
6974
  i + 2 < rawBuffer.length &&
6878
6975
  rawBuffer[i + 1] === 0xB8 &&
6879
6976
  rawBuffer[i + 2] >= 0x80 &&
6880
6977
  rawBuffer[i + 2] <= 0x8F) {
6881
- if (!hasVariationSelectors) {
6978
+ // Check if this is an emoji presentation selector (FE0F after emoji base)
6979
+ if (rawBuffer[i + 2] === 0x8F && isEmojiVariationSelector(rawBuffer, i)) {
6980
+ // Legitimate emoji — skip
6981
+ }
6982
+ else if (!hasVariationSelectors) {
6882
6983
  hasVariationSelectors = true;
6883
6984
  variationSelectorLine = currentLine;
6884
6985
  }
@@ -7129,17 +7230,37 @@ dist/
7129
7230
  let homoglyphChar = '';
7130
7231
  const contentForHomoglyph = content || rawBuffer.toString('utf-8');
7131
7232
  const homoglyphLines = contentForHomoglyph.split('\n');
7233
+ // Track markdown code fences: homoglyphs inside ```...``` blocks in .md files
7234
+ // are documentation examples, not executable code — skip them.
7235
+ const isMarkdown = relativePath.endsWith('.md') || relativePath.endsWith('.txt');
7236
+ let inCodeFence = false;
7132
7237
  for (let lineIdx = 0; lineIdx < homoglyphLines.length; lineIdx++) {
7133
7238
  const line = homoglyphLines[lineIdx];
7134
7239
  if (line.length > MAX_LINE_LENGTH)
7135
7240
  continue;
7241
+ // Track code fence boundaries in markdown files
7242
+ if (isMarkdown && line.trimStart().startsWith('```')) {
7243
+ inCodeFence = !inCodeFence;
7244
+ continue;
7245
+ }
7246
+ // Skip lines inside markdown code fences (documentation examples)
7247
+ if (isMarkdown && inCodeFence)
7248
+ continue;
7136
7249
  // Skip comment lines
7137
7250
  const trimmed = line.trimStart();
7138
7251
  if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
7139
7252
  continue;
7140
- for (const ch of line) {
7141
- const cp = ch.codePointAt(0);
7253
+ const chars = [...line];
7254
+ for (let ci = 0; ci < chars.length; ci++) {
7255
+ const cp = chars[ci].codePointAt(0);
7142
7256
  if (homoglyphCodepoints.has(cp)) {
7257
+ // Check if this Cyrillic char is in a Cyrillic text block (i18n)
7258
+ // vs mixed into a Latin word (homoglyph attack).
7259
+ // Look at neighboring characters: if surrounded by other Cyrillic
7260
+ // or non-Latin chars, it's legitimate i18n text.
7261
+ if (isCyrillicInCyrillicContext(chars, ci)) {
7262
+ continue; // Legitimate i18n — skip
7263
+ }
7143
7264
  homoglyphFound = true;
7144
7265
  homoglyphLine = lineIdx + 1;
7145
7266
  homoglyphChar = `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;