flagrix 0.1.1 → 0.1.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.
|
@@ -361,6 +361,91 @@ function collectEvidence(content, patterns, max = MAX_EVIDENCE_LINES) {
|
|
|
361
361
|
return evidence;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// node_modules/@flagrix/scanner-core/dist/utils/mask.js
|
|
365
|
+
var REGEX_ALLOWED_AFTER_CHAR = /* @__PURE__ */ new Set([..."=(:,[!&|?{};+*%^<>~"]);
|
|
366
|
+
var REGEX_ALLOWED_AFTER_WORD = /* @__PURE__ */ new Set(["return", "case", "typeof", "in", "of", "do", "void", "delete"]);
|
|
367
|
+
function maskRegexLiterals(content) {
|
|
368
|
+
return content.split("\n").map(maskLine).join("\n");
|
|
369
|
+
}
|
|
370
|
+
function maskLine(line) {
|
|
371
|
+
let out = "";
|
|
372
|
+
let i = 0;
|
|
373
|
+
while (i < line.length) {
|
|
374
|
+
const ch = line[i];
|
|
375
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
376
|
+
const end = stringEnd(line, i);
|
|
377
|
+
out += line.slice(i, end);
|
|
378
|
+
i = end;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (ch === "/" && line[i + 1] === "/") {
|
|
382
|
+
out += line.slice(i);
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
if (ch === "/" && regexCanStartAfter(out)) {
|
|
386
|
+
const end = regexEnd(line, i);
|
|
387
|
+
if (end !== -1) {
|
|
388
|
+
out += " ".repeat(end - i);
|
|
389
|
+
i = end;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
out += ch;
|
|
394
|
+
i++;
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
function regexCanStartAfter(before) {
|
|
399
|
+
const trimmed = before.trimEnd();
|
|
400
|
+
if (trimmed === "")
|
|
401
|
+
return true;
|
|
402
|
+
if (REGEX_ALLOWED_AFTER_CHAR.has(trimmed.slice(-1)))
|
|
403
|
+
return true;
|
|
404
|
+
const word = trimmed.match(/[A-Za-z_$][A-Za-z0-9_$]*$/)?.[0];
|
|
405
|
+
return word !== void 0 && REGEX_ALLOWED_AFTER_WORD.has(word);
|
|
406
|
+
}
|
|
407
|
+
function regexEnd(line, start) {
|
|
408
|
+
let i = start + 1;
|
|
409
|
+
let inClass = false;
|
|
410
|
+
let body = 0;
|
|
411
|
+
for (; i < line.length; i++) {
|
|
412
|
+
const ch = line[i];
|
|
413
|
+
if (ch === "\\") {
|
|
414
|
+
i++;
|
|
415
|
+
body++;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (inClass) {
|
|
419
|
+
if (ch === "]")
|
|
420
|
+
inClass = false;
|
|
421
|
+
} else if (ch === "[") {
|
|
422
|
+
inClass = true;
|
|
423
|
+
} else if (ch === "/") {
|
|
424
|
+
if (body === 0)
|
|
425
|
+
return -1;
|
|
426
|
+
let end = i + 1;
|
|
427
|
+
while (end < line.length && /[a-z]/i.test(line[end]))
|
|
428
|
+
end++;
|
|
429
|
+
return end;
|
|
430
|
+
}
|
|
431
|
+
body++;
|
|
432
|
+
}
|
|
433
|
+
return -1;
|
|
434
|
+
}
|
|
435
|
+
function stringEnd(line, start) {
|
|
436
|
+
const quote = line[start];
|
|
437
|
+
let i = start + 1;
|
|
438
|
+
for (; i < line.length; i++) {
|
|
439
|
+
if (line[i] === "\\") {
|
|
440
|
+
i++;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (line[i] === quote)
|
|
444
|
+
return i + 1;
|
|
445
|
+
}
|
|
446
|
+
return line.length;
|
|
447
|
+
}
|
|
448
|
+
|
|
364
449
|
// node_modules/@flagrix/scanner-core/dist/github/api-error.js
|
|
365
450
|
async function githubApiError(response, fallback = "GitHub API error") {
|
|
366
451
|
let apiMessage = "";
|
|
@@ -549,8 +634,9 @@ async function scanGitHubRepo(repo, options) {
|
|
|
549
634
|
continue;
|
|
550
635
|
const fileData = await contentResponse.json();
|
|
551
636
|
const content = atob(fileData.content || "");
|
|
637
|
+
const matchable = /\.(?:js|ts)$/.test(file.path) ? maskRegexLiterals(content) : content;
|
|
552
638
|
if (SCANNABLE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) {
|
|
553
|
-
fileContents.push({ path: file.path, content });
|
|
639
|
+
fileContents.push({ path: file.path, content: matchable });
|
|
554
640
|
}
|
|
555
641
|
if (file.path.endsWith("package.json")) {
|
|
556
642
|
const pkgFindings = await scanPackageJson(content, maliciousPackages, file.path);
|
|
@@ -567,10 +653,10 @@ async function scanGitHubRepo(repo, options) {
|
|
|
567
653
|
patternsMatched += pyFindings.length;
|
|
568
654
|
}
|
|
569
655
|
if (SCANNABLE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) {
|
|
570
|
-
const yaraFindings = applyYaraRules(
|
|
656
|
+
const yaraFindings = applyYaraRules(matchable, yaraRules, file.path);
|
|
571
657
|
findings.push(...yaraFindings);
|
|
572
658
|
patternsMatched += yaraFindings.length;
|
|
573
|
-
const obfuscationFindings = detectObfuscation(
|
|
659
|
+
const obfuscationFindings = detectObfuscation(matchable, file.path, loadedRuleIds);
|
|
574
660
|
findings.push(...obfuscationFindings);
|
|
575
661
|
patternsMatched += obfuscationFindings.length;
|
|
576
662
|
}
|
|
@@ -612,7 +698,8 @@ async function scanGitHubRepo(repo, options) {
|
|
|
612
698
|
patternsMatched += supplyChainFindings.length;
|
|
613
699
|
}
|
|
614
700
|
}
|
|
615
|
-
const
|
|
701
|
+
const allPaths = tree.tree.filter((i) => i.type === "blob").map((i) => i.path);
|
|
702
|
+
const integrityFindings = await detectCodeIntegrityIssues(fileContents, allPaths, repo);
|
|
616
703
|
findings.push(...integrityFindings);
|
|
617
704
|
patternsMatched += integrityFindings.length;
|
|
618
705
|
const riskScore = calculateRiskScore(findings);
|
|
@@ -1284,7 +1371,7 @@ function detectSuspiciousFileAccess(content, filePath, coveredRuleIds = /* @__PU
|
|
|
1284
1371
|
}
|
|
1285
1372
|
return findings;
|
|
1286
1373
|
}
|
|
1287
|
-
async function detectCodeIntegrityIssues(files, _repo) {
|
|
1374
|
+
async function detectCodeIntegrityIssues(files, allPaths, _repo) {
|
|
1288
1375
|
const findings = [];
|
|
1289
1376
|
for (const file of files) {
|
|
1290
1377
|
if (file.path.endsWith(".min.js") || file.path.endsWith(".min.css"))
|
|
@@ -1306,8 +1393,9 @@ async function detectCodeIntegrityIssues(files, _repo) {
|
|
|
1306
1393
|
}
|
|
1307
1394
|
}
|
|
1308
1395
|
}
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1396
|
+
const basename = (p) => p.split("/").pop() ?? p;
|
|
1397
|
+
const hasLicense = allPaths.some((p) => /^(LICENSE|COPYING)/i.test(basename(p)));
|
|
1398
|
+
if (!hasLicense && allPaths.length > 5) {
|
|
1311
1399
|
findings.push({
|
|
1312
1400
|
severity: "low",
|
|
1313
1401
|
type: "CODE_INTEGRITY_ISSUE",
|
|
@@ -1315,8 +1403,8 @@ async function detectCodeIntegrityIssues(files, _repo) {
|
|
|
1315
1403
|
codeExplanation: "\u{1F4C4} Missing license file. Legitimate open-source projects typically include a license. Absence may indicate a quickly assembled malicious repository."
|
|
1316
1404
|
});
|
|
1317
1405
|
}
|
|
1318
|
-
const hasReadme =
|
|
1319
|
-
if (!hasReadme &&
|
|
1406
|
+
const hasReadme = allPaths.some((p) => /^README/i.test(basename(p)));
|
|
1407
|
+
if (!hasReadme && allPaths.length > 5) {
|
|
1320
1408
|
findings.push({
|
|
1321
1409
|
severity: "low",
|
|
1322
1410
|
type: "CODE_INTEGRITY_ISSUE",
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
scanGitHubRepo,
|
|
12
12
|
scanGitHubUser,
|
|
13
13
|
wantJson
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-AS7DXMSG.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
17
|
import { parseArgs } from "util";
|
|
@@ -132,7 +132,7 @@ async function main() {
|
|
|
132
132
|
}
|
|
133
133
|
return runScanUser(target, { json: values.json, token: values.token });
|
|
134
134
|
case "mcp": {
|
|
135
|
-
const { runMcp } = await import("./mcp-
|
|
135
|
+
const { runMcp } = await import("./mcp-LS7GRB2C.js");
|
|
136
136
|
return runMcp();
|
|
137
137
|
}
|
|
138
138
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagrix",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Scan GitHub repos and profiles for malware before you clone — CLI and MCP server for the Flagrix scanner",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"zod": "^3.24.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@flagrix/scanner-core": "^0.1.
|
|
54
|
+
"@flagrix/scanner-core": "^0.1.2",
|
|
55
55
|
"@types/node": "^20.0.0",
|
|
56
56
|
"tsup": "^8.0.0",
|
|
57
57
|
"typescript": "^5.0.0",
|