hackmyagent 0.11.0 → 0.11.2
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/attack/payloads/supply-chain.d.ts +9 -0
- package/dist/attack/payloads/supply-chain.d.ts.map +1 -1
- package/dist/attack/payloads/supply-chain.js +124 -47
- package/dist/attack/payloads/supply-chain.js.map +1 -1
- package/dist/attack/payloads/tool-shadow.d.ts +8 -0
- package/dist/attack/payloads/tool-shadow.d.ts.map +1 -1
- package/dist/attack/payloads/tool-shadow.js +77 -57
- package/dist/attack/payloads/tool-shadow.js.map +1 -1
- package/dist/attack/scanner.d.ts.map +1 -1
- package/dist/attack/scanner.js +8 -0
- package/dist/attack/scanner.js.map +1 -1
- package/dist/cli.js +68 -16
- package/dist/cli.js.map +1 -1
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +152 -18
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/taxonomy.d.ts.map +1 -1
- package/dist/hardening/taxonomy.js +1 -0
- package/dist/hardening/taxonomy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/signcrypt.d.ts.map +1 -1
- package/dist/plugins/signcrypt.js +15 -0
- package/dist/plugins/signcrypt.js.map +1 -1
- package/package.json +1 -1
|
@@ -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;
|
|
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;CAuWxC"}
|
|
@@ -3909,7 +3909,7 @@ dist/
|
|
|
3909
3909
|
fixable: true,
|
|
3910
3910
|
fixed: skill001Fixed,
|
|
3911
3911
|
fixMessage: skill001Fixed ? 'Added SHA-256 signature block to skill file' : undefined,
|
|
3912
|
-
fix: '
|
|
3912
|
+
fix: 'Run `hackmyagent fix-all --with-aim` to automatically sign all skill files with a cryptographic identity',
|
|
3913
3913
|
});
|
|
3914
3914
|
// SKILL-002: Remote Fetch Pattern
|
|
3915
3915
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4391,7 +4391,7 @@ dist/
|
|
|
4391
4391
|
: 'Heartbeat lacks hash pinning - content integrity cannot be verified',
|
|
4392
4392
|
file: relativePath,
|
|
4393
4393
|
fixable: false,
|
|
4394
|
-
fix: '
|
|
4394
|
+
fix: 'Run `hackmyagent fix-all --with-aim` to automatically pin and sign heartbeat files',
|
|
4395
4395
|
});
|
|
4396
4396
|
// HEARTBEAT-003: Unsigned Heartbeat
|
|
4397
4397
|
const hasSignature = content.includes('opena2a_signature:') ||
|
|
@@ -4409,7 +4409,7 @@ dist/
|
|
|
4409
4409
|
: 'Heartbeat is unsigned - cannot verify authenticity or integrity',
|
|
4410
4410
|
file: relativePath,
|
|
4411
4411
|
fixable: false,
|
|
4412
|
-
fix: '
|
|
4412
|
+
fix: 'Run `hackmyagent fix-all --with-aim` to automatically sign all heartbeat files with a cryptographic identity',
|
|
4413
4413
|
});
|
|
4414
4414
|
// HEARTBEAT-004: Dangerous Capabilities
|
|
4415
4415
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6018,7 +6018,7 @@ dist/
|
|
|
6018
6018
|
message: `${idFile} declares identity without cryptographic key binding`,
|
|
6019
6019
|
fixable: false,
|
|
6020
6020
|
file: idFile,
|
|
6021
|
-
fix: '
|
|
6021
|
+
fix: 'Run `hackmyagent fix-all --with-aim` to bind identity to an Ed25519 key pair automatically',
|
|
6022
6022
|
});
|
|
6023
6023
|
}
|
|
6024
6024
|
}
|
|
@@ -6059,7 +6059,7 @@ dist/
|
|
|
6059
6059
|
message: 'Agent project has no identity declaration file (agent-card.json, agent.json, aim.json)',
|
|
6060
6060
|
fixable: false,
|
|
6061
6061
|
file: 'package.json',
|
|
6062
|
-
fix: '
|
|
6062
|
+
fix: 'Run `hackmyagent fix-all --with-aim` to create a cryptographic identity with Ed25519 key pair, audit logging, and trust scoring',
|
|
6063
6063
|
});
|
|
6064
6064
|
}
|
|
6065
6065
|
}
|
|
@@ -6097,7 +6097,7 @@ dist/
|
|
|
6097
6097
|
message: `${dnaFile} has no signature or content hash`,
|
|
6098
6098
|
fixable: false,
|
|
6099
6099
|
file: dnaFile,
|
|
6100
|
-
fix: '
|
|
6100
|
+
fix: 'Run `hackmyagent fix-all --with-aim` to automatically sign behavioral profiles with a cryptographic identity',
|
|
6101
6101
|
});
|
|
6102
6102
|
}
|
|
6103
6103
|
// DNA-003: No behavioral drift detection
|
|
@@ -6224,7 +6224,12 @@ dist/
|
|
|
6224
6224
|
*/
|
|
6225
6225
|
async checkUnicodeSteganography(targetDir, _autoFix) {
|
|
6226
6226
|
const findings = [];
|
|
6227
|
-
|
|
6227
|
+
// Scan a broad set of file types for unicode stego -- not just JS/TS
|
|
6228
|
+
const stegoExtensions = [
|
|
6229
|
+
'.js', '.ts', '.mjs', '.cjs', '.tsx', '.jsx',
|
|
6230
|
+
'.py', '.md', '.txt', '.yaml', '.yml', '.json', '.toml',
|
|
6231
|
+
];
|
|
6232
|
+
const sourceFiles = await this.walkDirectory(targetDir, stegoExtensions);
|
|
6228
6233
|
for (const filePath of sourceFiles) {
|
|
6229
6234
|
const relativePath = path.relative(targetDir, filePath);
|
|
6230
6235
|
let rawBuffer;
|
|
@@ -6238,18 +6243,69 @@ dist/
|
|
|
6238
6243
|
if (rawBuffer.length > MAX_FILE_SIZE)
|
|
6239
6244
|
continue;
|
|
6240
6245
|
// UNICODE-STEGO-001: Invisible Codepoint Detection
|
|
6241
|
-
// Scan for
|
|
6242
|
-
//
|
|
6246
|
+
// Scan for:
|
|
6247
|
+
// - Zero-width characters: U+200B (ZWSP), U+200C (ZWNJ), U+200D (ZWJ), U+FEFF (BOM mid-file)
|
|
6248
|
+
// - Variation selectors U+FE00-FE0F
|
|
6249
|
+
// - Tag characters U+E0100-E01EF
|
|
6250
|
+
// - Bidirectional override chars U+202A-U+202E and U+2066-U+2069
|
|
6243
6251
|
let hasVariationSelectors = false;
|
|
6244
6252
|
let variationSelectorLine = 1;
|
|
6245
6253
|
let hasTagCharsIn001 = false;
|
|
6246
6254
|
let tagCharLine001 = 1;
|
|
6255
|
+
let hasZeroWidth = false;
|
|
6256
|
+
let zeroWidthLine = 1;
|
|
6257
|
+
let hasBidiOverride = false;
|
|
6258
|
+
let bidiOverrideLine = 1;
|
|
6247
6259
|
let currentLine = 1;
|
|
6248
6260
|
for (let i = 0; i < rawBuffer.length; i++) {
|
|
6249
6261
|
if (rawBuffer[i] === 0x0A) {
|
|
6250
6262
|
currentLine++;
|
|
6251
6263
|
continue;
|
|
6252
6264
|
}
|
|
6265
|
+
// Zero-width characters: E2 80 8B (ZWSP), E2 80 8C (ZWNJ), E2 80 8D (ZWJ)
|
|
6266
|
+
if (rawBuffer[i] === 0xE2 &&
|
|
6267
|
+
i + 2 < rawBuffer.length &&
|
|
6268
|
+
rawBuffer[i + 1] === 0x80 &&
|
|
6269
|
+
rawBuffer[i + 2] >= 0x8B &&
|
|
6270
|
+
rawBuffer[i + 2] <= 0x8D) {
|
|
6271
|
+
if (!hasZeroWidth) {
|
|
6272
|
+
hasZeroWidth = true;
|
|
6273
|
+
zeroWidthLine = currentLine;
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
// BOM / Zero-width no-break space U+FEFF: EF BB BF (only suspicious mid-file)
|
|
6277
|
+
if (rawBuffer[i] === 0xEF &&
|
|
6278
|
+
i + 2 < rawBuffer.length &&
|
|
6279
|
+
rawBuffer[i + 1] === 0xBB &&
|
|
6280
|
+
rawBuffer[i + 2] === 0xBF &&
|
|
6281
|
+
i > 0) {
|
|
6282
|
+
if (!hasZeroWidth) {
|
|
6283
|
+
hasZeroWidth = true;
|
|
6284
|
+
zeroWidthLine = currentLine;
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
// Bidirectional override chars U+202A-U+202E: E2 80 AA-AE
|
|
6288
|
+
if (rawBuffer[i] === 0xE2 &&
|
|
6289
|
+
i + 2 < rawBuffer.length &&
|
|
6290
|
+
rawBuffer[i + 1] === 0x80 &&
|
|
6291
|
+
rawBuffer[i + 2] >= 0xAA &&
|
|
6292
|
+
rawBuffer[i + 2] <= 0xAE) {
|
|
6293
|
+
if (!hasBidiOverride) {
|
|
6294
|
+
hasBidiOverride = true;
|
|
6295
|
+
bidiOverrideLine = currentLine;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
// Bidirectional isolate chars U+2066-U+2069: E2 81 A6-A9
|
|
6299
|
+
if (rawBuffer[i] === 0xE2 &&
|
|
6300
|
+
i + 2 < rawBuffer.length &&
|
|
6301
|
+
rawBuffer[i + 1] === 0x81 &&
|
|
6302
|
+
rawBuffer[i + 2] >= 0xA6 &&
|
|
6303
|
+
rawBuffer[i + 2] <= 0xA9) {
|
|
6304
|
+
if (!hasBidiOverride) {
|
|
6305
|
+
hasBidiOverride = true;
|
|
6306
|
+
bidiOverrideLine = currentLine;
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6253
6309
|
// Variation selectors: EF B8 80-8F
|
|
6254
6310
|
if (rawBuffer[i] === 0xEF &&
|
|
6255
6311
|
i + 2 < rawBuffer.length &&
|
|
@@ -6273,24 +6329,39 @@ dist/
|
|
|
6273
6329
|
}
|
|
6274
6330
|
}
|
|
6275
6331
|
}
|
|
6276
|
-
if (hasVariationSelectors || hasTagCharsIn001) {
|
|
6332
|
+
if (hasZeroWidth || hasVariationSelectors || hasTagCharsIn001 || hasBidiOverride) {
|
|
6277
6333
|
const detectedTypes = [];
|
|
6334
|
+
if (hasZeroWidth)
|
|
6335
|
+
detectedTypes.push('zero-width characters (U+200B-U+200D, U+FEFF)');
|
|
6336
|
+
if (hasBidiOverride)
|
|
6337
|
+
detectedTypes.push('bidirectional overrides (U+202A-U+202E, U+2066-U+2069)');
|
|
6278
6338
|
if (hasVariationSelectors)
|
|
6279
6339
|
detectedTypes.push('variation selectors (U+FE00-FE0F)');
|
|
6280
6340
|
if (hasTagCharsIn001)
|
|
6281
6341
|
detectedTypes.push('tag characters (U+E0100-E01EF)');
|
|
6342
|
+
const lineNumbers = [];
|
|
6343
|
+
if (hasZeroWidth)
|
|
6344
|
+
lineNumbers.push(zeroWidthLine);
|
|
6345
|
+
if (hasBidiOverride)
|
|
6346
|
+
lineNumbers.push(bidiOverrideLine);
|
|
6347
|
+
if (hasVariationSelectors)
|
|
6348
|
+
lineNumbers.push(variationSelectorLine);
|
|
6349
|
+
if (hasTagCharsIn001)
|
|
6350
|
+
lineNumbers.push(tagCharLine001);
|
|
6351
|
+
const earliestLine = Math.min(...lineNumbers);
|
|
6352
|
+
const severity = hasBidiOverride || hasVariationSelectors || hasTagCharsIn001 ? 'critical' : 'high';
|
|
6282
6353
|
findings.push({
|
|
6283
6354
|
checkId: 'UNICODE-STEGO-001',
|
|
6284
6355
|
name: 'Invisible Unicode Codepoints Detected',
|
|
6285
|
-
description: 'Source file contains invisible Unicode codepoints that can hide malicious payloads (
|
|
6286
|
-
category: '
|
|
6287
|
-
severity
|
|
6356
|
+
description: 'Source file contains invisible Unicode codepoints that can hide malicious payloads (zero-width characters, bidirectional overrides, variation selectors, or tag characters)',
|
|
6357
|
+
category: 'supply-chain',
|
|
6358
|
+
severity,
|
|
6288
6359
|
passed: false,
|
|
6289
6360
|
message: `Found ${detectedTypes.join(' and ')} in ${relativePath}`,
|
|
6290
6361
|
file: relativePath,
|
|
6291
|
-
line:
|
|
6362
|
+
line: earliestLine,
|
|
6292
6363
|
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 -
|
|
6364
|
+
fix: 'Inspect the file with a hex editor (e.g., xxd) to identify and remove invisible Unicode codepoints. Run: xxd ' + relativePath + ' | grep -iE "e280 8[bcd]|efbb bf|e280 a[a-e]|e281 a[6-9]|efb8 8|f3a0"',
|
|
6294
6365
|
});
|
|
6295
6366
|
}
|
|
6296
6367
|
// UNICODE-STEGO-002: GlassWorm Decoder Pattern
|
|
@@ -6319,7 +6390,7 @@ dist/
|
|
|
6319
6390
|
checkId: 'UNICODE-STEGO-002',
|
|
6320
6391
|
name: 'GlassWorm Decoder Pattern Detected',
|
|
6321
6392
|
description: 'Source file contains .codePointAt() usage combined with Unicode variation selector or tag character hex literals - this is the decoder half of a GlassWorm attack',
|
|
6322
|
-
category: '
|
|
6393
|
+
category: 'supply-chain',
|
|
6323
6394
|
severity: 'critical',
|
|
6324
6395
|
passed: false,
|
|
6325
6396
|
message: `Found GlassWorm decoder pattern (.codePointAt + hex range literals) in ${relativePath}`,
|
|
@@ -6373,7 +6444,7 @@ dist/
|
|
|
6373
6444
|
checkId: 'UNICODE-STEGO-003',
|
|
6374
6445
|
name: 'Eval on String with Hidden Payload',
|
|
6375
6446
|
description: 'eval() or Function() is called with a string that has very few visible characters but a large byte footprint - indicates invisible Unicode payload',
|
|
6376
|
-
category: '
|
|
6447
|
+
category: 'supply-chain',
|
|
6377
6448
|
severity: 'critical',
|
|
6378
6449
|
passed: false,
|
|
6379
6450
|
message: `Found eval/Function with ${visibleChars} visible chars but ${byteLength} bytes in ${relativePath}`,
|
|
@@ -6415,7 +6486,7 @@ dist/
|
|
|
6415
6486
|
checkId: 'UNICODE-STEGO-004',
|
|
6416
6487
|
name: 'Unicode Tag Character Block Detected',
|
|
6417
6488
|
description: 'Source file contains characters from the Unicode Tag block (U+E0000-U+E01EF) which have no visible rendering and can be used to hide data',
|
|
6418
|
-
category: '
|
|
6489
|
+
category: 'supply-chain',
|
|
6419
6490
|
severity: 'high',
|
|
6420
6491
|
passed: false,
|
|
6421
6492
|
message: `Found Unicode tag block characters in ${relativePath}`,
|
|
@@ -6427,6 +6498,69 @@ dist/
|
|
|
6427
6498
|
}
|
|
6428
6499
|
}
|
|
6429
6500
|
}
|
|
6501
|
+
// UNICODE-STEGO-005: Homoglyph Substitution Detection
|
|
6502
|
+
// Detect Cyrillic/Greek/fullwidth letters that look identical to Latin letters.
|
|
6503
|
+
// Only scan code files where identifiers matter.
|
|
6504
|
+
const codeExtensions = new Set(['.js', '.ts', '.mjs', '.cjs', '.tsx', '.jsx', '.py']);
|
|
6505
|
+
const codeFiles = sourceFiles.filter((f) => codeExtensions.has(path.extname(f).toLowerCase()));
|
|
6506
|
+
const HOMOGLYPHS = {
|
|
6507
|
+
0x0430: 'a', 0x0435: 'e', 0x043E: 'o', 0x0440: 'p',
|
|
6508
|
+
0x0441: 'c', 0x0443: 'y', 0x0445: 'x', 0x0456: 'i',
|
|
6509
|
+
0x0455: 's', 0x04BB: 'h', 0x0501: 'd', 0x051B: 'q',
|
|
6510
|
+
0x03B1: 'a', 0x03BF: 'o', 0x03C1: 'p',
|
|
6511
|
+
0xFF41: 'a', 0xFF45: 'e', 0xFF4F: 'o',
|
|
6512
|
+
};
|
|
6513
|
+
const homoglyphCodes = new Set(Object.keys(HOMOGLYPHS).map(Number));
|
|
6514
|
+
for (const filePath of codeFiles) {
|
|
6515
|
+
const relativePath = path.relative(targetDir, filePath);
|
|
6516
|
+
let content;
|
|
6517
|
+
try {
|
|
6518
|
+
content = await fs.readFile(filePath, 'utf-8');
|
|
6519
|
+
}
|
|
6520
|
+
catch {
|
|
6521
|
+
continue;
|
|
6522
|
+
}
|
|
6523
|
+
if (content.length > MAX_FILE_SIZE)
|
|
6524
|
+
continue;
|
|
6525
|
+
const lines = content.split('\n');
|
|
6526
|
+
const foundHomoglyphs = [];
|
|
6527
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
6528
|
+
const line = lines[lineIdx];
|
|
6529
|
+
if (line.length > MAX_LINE_LENGTH)
|
|
6530
|
+
continue;
|
|
6531
|
+
const trimmed = line.trimStart();
|
|
6532
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
|
|
6533
|
+
continue;
|
|
6534
|
+
for (const ch of line) {
|
|
6535
|
+
const cp = ch.codePointAt(0);
|
|
6536
|
+
if (homoglyphCodes.has(cp)) {
|
|
6537
|
+
foundHomoglyphs.push({ line: lineIdx + 1, lookalike: HOMOGLYPHS[cp], cp });
|
|
6538
|
+
break;
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
if (foundHomoglyphs.length >= 5)
|
|
6542
|
+
break;
|
|
6543
|
+
}
|
|
6544
|
+
if (foundHomoglyphs.length > 0) {
|
|
6545
|
+
const examples = foundHomoglyphs
|
|
6546
|
+
.slice(0, 3)
|
|
6547
|
+
.map((h) => `U+${h.cp.toString(16).toUpperCase().padStart(4, '0')} (looks like '${h.lookalike}') at line ${h.line}`)
|
|
6548
|
+
.join(', ');
|
|
6549
|
+
findings.push({
|
|
6550
|
+
checkId: 'UNICODE-STEGO-005',
|
|
6551
|
+
name: 'Homoglyph Character Substitution Detected',
|
|
6552
|
+
description: 'Source file contains non-Latin characters (Cyrillic, Greek, or fullwidth) that visually mimic Latin letters. This can disguise malicious identifiers or imports.',
|
|
6553
|
+
category: 'supply-chain',
|
|
6554
|
+
severity: 'high',
|
|
6555
|
+
passed: false,
|
|
6556
|
+
message: `Found ${foundHomoglyphs.length} homoglyph substitution(s) in ${relativePath}: ${examples}`,
|
|
6557
|
+
file: relativePath,
|
|
6558
|
+
line: foundHomoglyphs[0].line,
|
|
6559
|
+
fixable: false,
|
|
6560
|
+
fix: 'Replace non-Latin lookalike characters with their ASCII equivalents. Run: node -e "const s=require(\'fs\').readFileSync(\'' + relativePath + '\',\'utf8\');[...s].forEach((c,i)=>{const cp=c.codePointAt(0);if(cp>0x7F&&cp<0xFFFF)console.log(\'offset\',i,\'U+\'+cp.toString(16),JSON.stringify(c))})"',
|
|
6561
|
+
});
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6430
6564
|
return findings;
|
|
6431
6565
|
}
|
|
6432
6566
|
}
|