@aspect-guard/core 0.1.0 → 0.4.0
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/index.d.ts +322 -2
- package/dist/index.js +1380 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import * as path from "path";
|
|
|
24
24
|
var IDE_PATHS = {
|
|
25
25
|
"VS Code": ["~/.vscode/extensions"],
|
|
26
26
|
"VS Code Insiders": ["~/.vscode-insiders/extensions"],
|
|
27
|
+
"VS Code Server": ["~/.vscode-server/extensions"],
|
|
27
28
|
Cursor: ["~/.cursor/extensions"],
|
|
28
29
|
Windsurf: ["~/.windsurf/extensions"],
|
|
29
30
|
Trae: ["~/.trae/extensions"],
|
|
@@ -133,9 +134,7 @@ async function readExtensionsFromDirectory(directoryPath) {
|
|
|
133
134
|
const entries = await fs2.readdir(directoryPath, { withFileTypes: true });
|
|
134
135
|
const directories = entries.filter((entry) => entry.isDirectory());
|
|
135
136
|
const results = await Promise.all(
|
|
136
|
-
directories.map(
|
|
137
|
-
(dir) => readExtension(path2.join(directoryPath, dir.name))
|
|
138
|
-
)
|
|
137
|
+
directories.map((dir) => readExtension(path2.join(directoryPath, dir.name)))
|
|
139
138
|
);
|
|
140
139
|
for (const result of results) {
|
|
141
140
|
if (result) {
|
|
@@ -151,22 +150,8 @@ async function readExtensionsFromDirectory(directoryPath) {
|
|
|
151
150
|
// src/scanner/file-collector.ts
|
|
152
151
|
import * as fs3 from "fs/promises";
|
|
153
152
|
import * as path3 from "path";
|
|
154
|
-
var COLLECTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
155
|
-
|
|
156
|
-
".ts",
|
|
157
|
-
".jsx",
|
|
158
|
-
".tsx",
|
|
159
|
-
".mjs",
|
|
160
|
-
".cjs",
|
|
161
|
-
".json"
|
|
162
|
-
]);
|
|
163
|
-
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
164
|
-
"node_modules",
|
|
165
|
-
".git",
|
|
166
|
-
".svn",
|
|
167
|
-
".hg",
|
|
168
|
-
"__pycache__"
|
|
169
|
-
]);
|
|
153
|
+
var COLLECTED_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs", ".json"]);
|
|
154
|
+
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", ".git", ".svn", ".hg", "__pycache__"]);
|
|
170
155
|
var IGNORED_PATTERNS = [/\.min\.js$/, /\.map$/, /\.d\.ts$/];
|
|
171
156
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
172
157
|
function shouldCollectFile(filePath) {
|
|
@@ -304,6 +289,617 @@ var RuleEngine = class {
|
|
|
304
289
|
}
|
|
305
290
|
};
|
|
306
291
|
|
|
292
|
+
// src/data/trusted-publishers.ts
|
|
293
|
+
var TRUSTED_PUBLISHERS = [
|
|
294
|
+
// Microsoft official
|
|
295
|
+
"ms-python",
|
|
296
|
+
"ms-vscode",
|
|
297
|
+
"ms-dotnettools",
|
|
298
|
+
"ms-azuretools",
|
|
299
|
+
"ms-toolsai",
|
|
300
|
+
"microsoft",
|
|
301
|
+
"vscode",
|
|
302
|
+
// GitHub
|
|
303
|
+
"github",
|
|
304
|
+
// Major language support
|
|
305
|
+
"golang",
|
|
306
|
+
"rust-lang",
|
|
307
|
+
"redhat",
|
|
308
|
+
"oracle",
|
|
309
|
+
"julialang",
|
|
310
|
+
// Major tool vendors
|
|
311
|
+
"esbenp",
|
|
312
|
+
// Prettier
|
|
313
|
+
"dbaeumer",
|
|
314
|
+
// ESLint
|
|
315
|
+
"eamodio",
|
|
316
|
+
// GitLens
|
|
317
|
+
// Cloud providers
|
|
318
|
+
"amazonwebservices",
|
|
319
|
+
"googlecloudtools",
|
|
320
|
+
"hashicorp",
|
|
321
|
+
// Other major vendors
|
|
322
|
+
"jetbrains",
|
|
323
|
+
"docker",
|
|
324
|
+
"mongodb",
|
|
325
|
+
"prisma",
|
|
326
|
+
"vscjava",
|
|
327
|
+
// Microsoft Java
|
|
328
|
+
// Popular developer tool vendors
|
|
329
|
+
"formulahendry",
|
|
330
|
+
// Code Runner (10M+ downloads)
|
|
331
|
+
"ritwickdey",
|
|
332
|
+
// Live Server (50M+ downloads)
|
|
333
|
+
"humao",
|
|
334
|
+
// REST Client (5M+ downloads)
|
|
335
|
+
"rangav",
|
|
336
|
+
// Thunder Client (3M+ downloads)
|
|
337
|
+
"mtxr",
|
|
338
|
+
// SQLTools
|
|
339
|
+
"cweijan",
|
|
340
|
+
// Database Client
|
|
341
|
+
// Remote development
|
|
342
|
+
"ms-vscode-remote",
|
|
343
|
+
// Remote-SSH, Dev Containers, WSL
|
|
344
|
+
// Testing
|
|
345
|
+
"hbenl",
|
|
346
|
+
// Test Explorer UI
|
|
347
|
+
"firsttris",
|
|
348
|
+
// Jest Runner
|
|
349
|
+
"orta",
|
|
350
|
+
// vscode-jest
|
|
351
|
+
// Notebooks
|
|
352
|
+
"ms-toolsai"
|
|
353
|
+
// Jupyter
|
|
354
|
+
];
|
|
355
|
+
var TRUSTED_EXTENSION_IDS = [
|
|
356
|
+
// These are explicitly trusted extension IDs
|
|
357
|
+
"github.copilot",
|
|
358
|
+
"github.copilot-chat",
|
|
359
|
+
"ms-python.python",
|
|
360
|
+
"ms-python.vscode-pylance",
|
|
361
|
+
"ms-vscode.cpptools",
|
|
362
|
+
"golang.go",
|
|
363
|
+
"rust-lang.rust-analyzer"
|
|
364
|
+
];
|
|
365
|
+
function isTrustedPublisher(publisher) {
|
|
366
|
+
const normalized = publisher.toLowerCase();
|
|
367
|
+
return TRUSTED_PUBLISHERS.some((p) => normalized === p.toLowerCase());
|
|
368
|
+
}
|
|
369
|
+
function isTrustedExtension(extensionId) {
|
|
370
|
+
const normalized = extensionId.toLowerCase();
|
|
371
|
+
return TRUSTED_EXTENSION_IDS.some((id) => normalized === id.toLowerCase());
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/data/verified-publishers.ts
|
|
375
|
+
var VERIFIED_PUBLISHERS = [
|
|
376
|
+
// Microsoft & GitHub (already trusted, but also verified)
|
|
377
|
+
"microsoft",
|
|
378
|
+
"ms-python",
|
|
379
|
+
"ms-vscode",
|
|
380
|
+
"ms-dotnettools",
|
|
381
|
+
"ms-azuretools",
|
|
382
|
+
"ms-toolsai",
|
|
383
|
+
"ms-vscode-remote",
|
|
384
|
+
"ms-kubernetes-tools",
|
|
385
|
+
"ms-playwright",
|
|
386
|
+
"vscode",
|
|
387
|
+
"github",
|
|
388
|
+
"vscjava",
|
|
389
|
+
// Major cloud providers (verified)
|
|
390
|
+
"amazonwebservices",
|
|
391
|
+
"googlecloudtools",
|
|
392
|
+
"hashicorp",
|
|
393
|
+
// Major language/framework vendors (verified)
|
|
394
|
+
"golang",
|
|
395
|
+
"rust-lang",
|
|
396
|
+
"redhat",
|
|
397
|
+
"oracle",
|
|
398
|
+
"julialang",
|
|
399
|
+
"dart-code",
|
|
400
|
+
"flutter",
|
|
401
|
+
"svelte",
|
|
402
|
+
"vue",
|
|
403
|
+
"angular",
|
|
404
|
+
"astro-build",
|
|
405
|
+
// Major tool vendors (verified)
|
|
406
|
+
"jetbrains",
|
|
407
|
+
"docker",
|
|
408
|
+
"mongodb",
|
|
409
|
+
"prisma",
|
|
410
|
+
"atlassian",
|
|
411
|
+
"gitlab",
|
|
412
|
+
"sourcegraph",
|
|
413
|
+
"snyk-security",
|
|
414
|
+
"sonarqube",
|
|
415
|
+
"datadog",
|
|
416
|
+
// Popular extension vendors (verified)
|
|
417
|
+
"davidanson",
|
|
418
|
+
// markdownlint
|
|
419
|
+
"esbenp",
|
|
420
|
+
// Prettier
|
|
421
|
+
"dbaeumer",
|
|
422
|
+
// ESLint
|
|
423
|
+
"eamodio",
|
|
424
|
+
// GitLens
|
|
425
|
+
"streetsidesoftware",
|
|
426
|
+
// Code Spell Checker
|
|
427
|
+
"editorconfig",
|
|
428
|
+
"christian-kohler",
|
|
429
|
+
// Path Intellisense
|
|
430
|
+
"visualstudioexptteam",
|
|
431
|
+
// IntelliCode
|
|
432
|
+
"bierner",
|
|
433
|
+
// Markdown Preview
|
|
434
|
+
"jock",
|
|
435
|
+
// SVG Viewer
|
|
436
|
+
"pkief",
|
|
437
|
+
// Material Icon Theme
|
|
438
|
+
"zhuangtongfa",
|
|
439
|
+
// One Dark Pro
|
|
440
|
+
"mechatroner"
|
|
441
|
+
// Rainbow CSV
|
|
442
|
+
];
|
|
443
|
+
function isVerifiedPublisher(publisher) {
|
|
444
|
+
const normalized = publisher.toLowerCase();
|
|
445
|
+
return VERIFIED_PUBLISHERS.some((p) => normalized === p.toLowerCase());
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/data/popular-extensions.ts
|
|
449
|
+
var MEGA_POPULAR_EXTENSIONS = [
|
|
450
|
+
// Python ecosystem
|
|
451
|
+
{ id: "ms-python.python", downloads: 12e7, tier: "mega" },
|
|
452
|
+
{ id: "ms-python.vscode-pylance", downloads: 8e7, tier: "mega" },
|
|
453
|
+
// C/C++
|
|
454
|
+
{ id: "ms-vscode.cpptools", downloads: 6e7, tier: "mega" },
|
|
455
|
+
// GitHub Copilot
|
|
456
|
+
{ id: "github.copilot", downloads: 5e7, tier: "mega" },
|
|
457
|
+
{ id: "github.copilot-chat", downloads: 3e7, tier: "mega" },
|
|
458
|
+
// ESLint & Prettier
|
|
459
|
+
{ id: "dbaeumer.vscode-eslint", downloads: 45e6, tier: "mega" },
|
|
460
|
+
{ id: "esbenp.prettier-vscode", downloads: 5e7, tier: "mega" },
|
|
461
|
+
// Live Server
|
|
462
|
+
{ id: "ritwickdey.liveserver", downloads: 6e7, tier: "mega" },
|
|
463
|
+
// GitLens
|
|
464
|
+
{ id: "eamodio.gitlens", downloads: 4e7, tier: "mega" },
|
|
465
|
+
// Docker
|
|
466
|
+
{ id: "ms-azuretools.vscode-docker", downloads: 35e6, tier: "mega" },
|
|
467
|
+
// Remote Development
|
|
468
|
+
{ id: "ms-vscode-remote.remote-ssh", downloads: 25e6, tier: "mega" },
|
|
469
|
+
{ id: "ms-vscode-remote.remote-containers", downloads: 2e7, tier: "mega" },
|
|
470
|
+
{ id: "ms-vscode-remote.remote-wsl", downloads: 2e7, tier: "mega" },
|
|
471
|
+
// IntelliCode
|
|
472
|
+
{ id: "visualstudioexptteam.vscodeintellicode", downloads: 4e7, tier: "mega" },
|
|
473
|
+
// Path Intellisense
|
|
474
|
+
{ id: "christian-kohler.path-intellisense", downloads: 2e7, tier: "mega" },
|
|
475
|
+
// Material Icon Theme
|
|
476
|
+
{ id: "pkief.material-icon-theme", downloads: 3e7, tier: "mega" },
|
|
477
|
+
// One Dark Pro
|
|
478
|
+
{ id: "zhuangtongfa.material-theme", downloads: 15e6, tier: "mega" },
|
|
479
|
+
// Bracket Pair Colorizer (now built-in but still installed)
|
|
480
|
+
{ id: "coenraads.bracket-pair-colorizer-2", downloads: 12e6, tier: "mega" },
|
|
481
|
+
// Code Runner
|
|
482
|
+
{ id: "formulahendry.code-runner", downloads: 2e7, tier: "mega" },
|
|
483
|
+
// Auto Rename Tag
|
|
484
|
+
{ id: "formulahendry.auto-rename-tag", downloads: 18e6, tier: "mega" },
|
|
485
|
+
// Code Spell Checker
|
|
486
|
+
{ id: "streetsidesoftware.code-spell-checker", downloads: 15e6, tier: "mega" },
|
|
487
|
+
// Markdown All in One
|
|
488
|
+
{ id: "yzhang.markdown-all-in-one", downloads: 12e6, tier: "mega" },
|
|
489
|
+
// Debugger for Chrome (deprecated but still installed)
|
|
490
|
+
{ id: "msjsdiag.debugger-for-chrome", downloads: 25e6, tier: "mega" },
|
|
491
|
+
// Go
|
|
492
|
+
{ id: "golang.go", downloads: 15e6, tier: "mega" },
|
|
493
|
+
// Java
|
|
494
|
+
{ id: "redhat.java", downloads: 2e7, tier: "mega" },
|
|
495
|
+
{ id: "vscjava.vscode-java-debug", downloads: 15e6, tier: "mega" },
|
|
496
|
+
{ id: "vscjava.vscode-java-pack", downloads: 18e6, tier: "mega" },
|
|
497
|
+
// C#
|
|
498
|
+
{ id: "ms-dotnettools.csharp", downloads: 25e6, tier: "mega" }
|
|
499
|
+
];
|
|
500
|
+
var POPULAR_EXTENSIONS = [
|
|
501
|
+
// REST Clients
|
|
502
|
+
{ id: "humao.rest-client", downloads: 5e6, tier: "popular" },
|
|
503
|
+
{ id: "rangav.vscode-thunder-client", downloads: 3e6, tier: "popular" },
|
|
504
|
+
// Database
|
|
505
|
+
{ id: "mtxr.sqltools", downloads: 4e6, tier: "popular" },
|
|
506
|
+
{ id: "cweijan.vscode-database-client2", downloads: 2e6, tier: "popular" },
|
|
507
|
+
// Jupyter
|
|
508
|
+
{ id: "ms-toolsai.jupyter", downloads: 8e6, tier: "popular" },
|
|
509
|
+
{ id: "ms-toolsai.jupyter-keymap", downloads: 5e6, tier: "popular" },
|
|
510
|
+
// Kubernetes
|
|
511
|
+
{ id: "ms-kubernetes-tools.vscode-kubernetes-tools", downloads: 4e6, tier: "popular" },
|
|
512
|
+
// Vue / React / Angular
|
|
513
|
+
{ id: "vue.volar", downloads: 8e6, tier: "popular" },
|
|
514
|
+
{ id: "dsznajder.es7-react-js-snippets", downloads: 9e6, tier: "popular" },
|
|
515
|
+
{ id: "angular.ng-template", downloads: 5e6, tier: "popular" },
|
|
516
|
+
// Svelte
|
|
517
|
+
{ id: "svelte.svelte-vscode", downloads: 3e6, tier: "popular" },
|
|
518
|
+
// Astro
|
|
519
|
+
{ id: "astro-build.astro-vscode", downloads: 2e6, tier: "popular" },
|
|
520
|
+
// Tailwind CSS
|
|
521
|
+
{ id: "bradlc.vscode-tailwindcss", downloads: 8e6, tier: "popular" },
|
|
522
|
+
// YAML
|
|
523
|
+
{ id: "redhat.vscode-yaml", downloads: 9e6, tier: "popular" },
|
|
524
|
+
// XML
|
|
525
|
+
{ id: "redhat.vscode-xml", downloads: 6e6, tier: "popular" },
|
|
526
|
+
// Rust
|
|
527
|
+
{ id: "rust-lang.rust-analyzer", downloads: 5e6, tier: "popular" },
|
|
528
|
+
// PowerShell
|
|
529
|
+
{ id: "ms-vscode.powershell", downloads: 8e6, tier: "popular" },
|
|
530
|
+
// Terraform
|
|
531
|
+
{ id: "hashicorp.terraform", downloads: 9e6, tier: "popular" },
|
|
532
|
+
// TODO Highlight
|
|
533
|
+
{ id: "wayou.vscode-todo-highlight", downloads: 6e6, tier: "popular" },
|
|
534
|
+
// Bookmarks
|
|
535
|
+
{ id: "alefragnani.bookmarks", downloads: 5e6, tier: "popular" },
|
|
536
|
+
// Project Manager
|
|
537
|
+
{ id: "alefragnani.project-manager", downloads: 4e6, tier: "popular" },
|
|
538
|
+
// Rainbow CSV
|
|
539
|
+
{ id: "mechatroner.rainbow-csv", downloads: 4e6, tier: "popular" },
|
|
540
|
+
// Markdown Preview
|
|
541
|
+
{ id: "bierner.markdown-preview-github-styles", downloads: 3e6, tier: "popular" },
|
|
542
|
+
// markdownlint
|
|
543
|
+
{ id: "davidanson.vscode-markdownlint", downloads: 6e6, tier: "popular" },
|
|
544
|
+
// Error Lens
|
|
545
|
+
{ id: "usernamehw.errorlens", downloads: 5e6, tier: "popular" },
|
|
546
|
+
// Playwright
|
|
547
|
+
{ id: "ms-playwright.playwright", downloads: 2e6, tier: "popular" },
|
|
548
|
+
// Jest
|
|
549
|
+
{ id: "orta.vscode-jest", downloads: 3e6, tier: "popular" },
|
|
550
|
+
// Swagger / OpenAPI
|
|
551
|
+
{ id: "arjun.swagger-viewer", downloads: 2e6, tier: "popular" },
|
|
552
|
+
// Figma
|
|
553
|
+
{ id: "figma.figma-vscode-extension", downloads: 1e6, tier: "popular" },
|
|
554
|
+
// Atlassian
|
|
555
|
+
{ id: "atlassian.atlascode", downloads: 2e6, tier: "popular" }
|
|
556
|
+
];
|
|
557
|
+
var ALL_POPULAR_EXTENSIONS = [
|
|
558
|
+
...MEGA_POPULAR_EXTENSIONS,
|
|
559
|
+
...POPULAR_EXTENSIONS
|
|
560
|
+
];
|
|
561
|
+
function getPopularityTier(extensionId) {
|
|
562
|
+
const normalized = extensionId.toLowerCase();
|
|
563
|
+
const ext = ALL_POPULAR_EXTENSIONS.find((e) => e.id.toLowerCase() === normalized);
|
|
564
|
+
return ext?.tier ?? null;
|
|
565
|
+
}
|
|
566
|
+
function isMegaPopular(extensionId) {
|
|
567
|
+
return getPopularityTier(extensionId) === "mega";
|
|
568
|
+
}
|
|
569
|
+
function isPopular(extensionId) {
|
|
570
|
+
return getPopularityTier(extensionId) !== null;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/rules/finding-adjuster.ts
|
|
574
|
+
var DOWNGRADE_MAP = {
|
|
575
|
+
critical: "medium",
|
|
576
|
+
high: "low",
|
|
577
|
+
medium: "info",
|
|
578
|
+
low: "info",
|
|
579
|
+
info: "info"
|
|
580
|
+
};
|
|
581
|
+
var EXPECTED_BEHAVIORS = {
|
|
582
|
+
"ai-assistant": [
|
|
583
|
+
{
|
|
584
|
+
// AI tools legitimately use child_process, eval for code execution
|
|
585
|
+
ruleIds: ["EG-CRIT-002"],
|
|
586
|
+
matchedPatterns: [
|
|
587
|
+
"child_process-exec",
|
|
588
|
+
"child_process-execSync",
|
|
589
|
+
"child_process-spawn-shell",
|
|
590
|
+
"eval",
|
|
591
|
+
"Function-constructor",
|
|
592
|
+
"dynamic-require"
|
|
593
|
+
]
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
// AI tools legitimately make network requests
|
|
597
|
+
ruleIds: ["EG-HIGH-002"],
|
|
598
|
+
matchedPatterns: ["dynamic-url", "unusual-port"]
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
// AI tools often read .env for API keys
|
|
602
|
+
ruleIds: ["EG-CRIT-003"],
|
|
603
|
+
matchedPatterns: ["env-file"]
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
// AI tools collect system info for telemetry / model context
|
|
607
|
+
// This includes os.hostname, os.platform, os.userInfo, process.env, etc.
|
|
608
|
+
ruleIds: ["EG-CRIT-001"]
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
// AI tools may have obfuscated/minified bundled code
|
|
612
|
+
ruleIds: ["EG-HIGH-001"],
|
|
613
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
theme: [
|
|
617
|
+
{
|
|
618
|
+
// Theme extensions with bundled JS may trigger obfuscation (false positive)
|
|
619
|
+
ruleIds: ["EG-HIGH-001"],
|
|
620
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
621
|
+
}
|
|
622
|
+
],
|
|
623
|
+
language: [
|
|
624
|
+
{
|
|
625
|
+
// Grammar extensions often have high-entropy bundled code or regex-heavy files
|
|
626
|
+
ruleIds: ["EG-HIGH-001"],
|
|
627
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
// Grammar files use words like "token", "secret" in syntax definitions
|
|
631
|
+
ruleIds: ["EG-HIGH-006"],
|
|
632
|
+
matchedPatterns: ["generic-secret"]
|
|
633
|
+
}
|
|
634
|
+
],
|
|
635
|
+
scm: [
|
|
636
|
+
{
|
|
637
|
+
// SCM extensions legitimately access git credentials
|
|
638
|
+
ruleIds: ["EG-CRIT-003"],
|
|
639
|
+
matchedPatterns: ["git-credentials"]
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
// SCM extensions may spawn git processes
|
|
643
|
+
ruleIds: ["EG-CRIT-002"],
|
|
644
|
+
matchedPatterns: ["child_process-exec", "child_process-execSync", "child_process-spawn-shell"]
|
|
645
|
+
}
|
|
646
|
+
],
|
|
647
|
+
debugger: [
|
|
648
|
+
{
|
|
649
|
+
// Debuggers legitimately spawn processes
|
|
650
|
+
ruleIds: ["EG-CRIT-002"],
|
|
651
|
+
matchedPatterns: [
|
|
652
|
+
"child_process-exec",
|
|
653
|
+
"child_process-execSync",
|
|
654
|
+
"child_process-spawn-shell"
|
|
655
|
+
]
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
// Debuggers may use dynamic require for adapter loading
|
|
659
|
+
ruleIds: ["EG-CRIT-002"],
|
|
660
|
+
matchedPatterns: ["dynamic-require"]
|
|
661
|
+
}
|
|
662
|
+
],
|
|
663
|
+
linter: [
|
|
664
|
+
{
|
|
665
|
+
// Linters legitimately spawn processes (eslint, prettier, etc.)
|
|
666
|
+
ruleIds: ["EG-CRIT-002"],
|
|
667
|
+
matchedPatterns: [
|
|
668
|
+
"child_process-exec",
|
|
669
|
+
"child_process-execSync",
|
|
670
|
+
"child_process-spawn-shell"
|
|
671
|
+
]
|
|
672
|
+
}
|
|
673
|
+
],
|
|
674
|
+
"language-support": [
|
|
675
|
+
{
|
|
676
|
+
// Language servers spawn interpreters/compilers (python, go, rustc, javac)
|
|
677
|
+
ruleIds: ["EG-CRIT-002"],
|
|
678
|
+
matchedPatterns: [
|
|
679
|
+
"child_process-exec",
|
|
680
|
+
"child_process-execSync",
|
|
681
|
+
"child_process-spawn-shell",
|
|
682
|
+
"dynamic-require"
|
|
683
|
+
]
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
// Language servers may collect system info for environment detection
|
|
687
|
+
ruleIds: ["EG-CRIT-001"]
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
// Language servers may have bundled code with high entropy
|
|
691
|
+
ruleIds: ["EG-HIGH-001"],
|
|
692
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
// Language servers may make network requests for package management
|
|
696
|
+
ruleIds: ["EG-HIGH-002"],
|
|
697
|
+
matchedPatterns: ["dynamic-url"]
|
|
698
|
+
}
|
|
699
|
+
],
|
|
700
|
+
"developer-tools": [
|
|
701
|
+
{
|
|
702
|
+
// Code runners spawn processes to execute code (python, node, bash, etc.)
|
|
703
|
+
ruleIds: ["EG-CRIT-002"],
|
|
704
|
+
matchedPatterns: [
|
|
705
|
+
"child_process-exec",
|
|
706
|
+
"child_process-execSync",
|
|
707
|
+
"child_process-spawn-shell",
|
|
708
|
+
"eval",
|
|
709
|
+
"Function-constructor"
|
|
710
|
+
]
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
// REST clients, API testers make network requests by design
|
|
714
|
+
ruleIds: ["EG-HIGH-002"],
|
|
715
|
+
matchedPatterns: ["dynamic-url", "http-to-ip", "unusual-port"]
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
// Live servers spawn HTTP servers on various ports
|
|
719
|
+
ruleIds: ["EG-HIGH-002"],
|
|
720
|
+
matchedPatterns: ["unusual-port"]
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
// Developer tools may collect system info for environment detection
|
|
724
|
+
ruleIds: ["EG-CRIT-001"]
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
// Developer tools may have bundled code with high entropy
|
|
728
|
+
ruleIds: ["EG-HIGH-001"],
|
|
729
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
730
|
+
}
|
|
731
|
+
],
|
|
732
|
+
"remote-development": [
|
|
733
|
+
{
|
|
734
|
+
// Remote extensions spawn SSH, docker, WSL processes
|
|
735
|
+
ruleIds: ["EG-CRIT-002"],
|
|
736
|
+
matchedPatterns: [
|
|
737
|
+
"child_process-exec",
|
|
738
|
+
"child_process-execSync",
|
|
739
|
+
"child_process-spawn-shell"
|
|
740
|
+
]
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
// Remote extensions make network connections by design
|
|
744
|
+
ruleIds: ["EG-HIGH-002"],
|
|
745
|
+
matchedPatterns: ["dynamic-url", "http-to-ip", "unusual-port"]
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
// Remote extensions collect system info for environment detection
|
|
749
|
+
ruleIds: ["EG-CRIT-001"]
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
// Remote extensions may access SSH keys and credentials legitimately
|
|
753
|
+
ruleIds: ["EG-CRIT-003"],
|
|
754
|
+
matchedPatterns: ["ssh-keys", "ssh-config"]
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
// Remote extensions may have bundled code
|
|
758
|
+
ruleIds: ["EG-HIGH-001"],
|
|
759
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
760
|
+
}
|
|
761
|
+
],
|
|
762
|
+
testing: [
|
|
763
|
+
{
|
|
764
|
+
// Test runners spawn test processes
|
|
765
|
+
ruleIds: ["EG-CRIT-002"],
|
|
766
|
+
matchedPatterns: [
|
|
767
|
+
"child_process-exec",
|
|
768
|
+
"child_process-execSync",
|
|
769
|
+
"child_process-spawn-shell"
|
|
770
|
+
]
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
// Test runners may collect system info for environment detection
|
|
774
|
+
ruleIds: ["EG-CRIT-001"]
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
// Test runners may have bundled code
|
|
778
|
+
ruleIds: ["EG-HIGH-001"],
|
|
779
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
780
|
+
}
|
|
781
|
+
],
|
|
782
|
+
notebook: [
|
|
783
|
+
{
|
|
784
|
+
// Notebook extensions spawn kernels (Python, Julia, etc.)
|
|
785
|
+
ruleIds: ["EG-CRIT-002"],
|
|
786
|
+
matchedPatterns: [
|
|
787
|
+
"child_process-exec",
|
|
788
|
+
"child_process-execSync",
|
|
789
|
+
"child_process-spawn-shell",
|
|
790
|
+
"dynamic-require"
|
|
791
|
+
]
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
// Notebook extensions make network connections for package management
|
|
795
|
+
ruleIds: ["EG-HIGH-002"],
|
|
796
|
+
matchedPatterns: ["dynamic-url", "unusual-port"]
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
// Notebook extensions collect system info for environment detection
|
|
800
|
+
ruleIds: ["EG-CRIT-001"]
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
// Notebook extensions may read environment files for kernel config
|
|
804
|
+
ruleIds: ["EG-CRIT-003"],
|
|
805
|
+
matchedPatterns: ["env-file"]
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
// Notebook extensions may have bundled code
|
|
809
|
+
ruleIds: ["EG-HIGH-001"],
|
|
810
|
+
matchedPatterns: ["high-entropy", "large-base64"]
|
|
811
|
+
}
|
|
812
|
+
]
|
|
813
|
+
};
|
|
814
|
+
function matchesExpectedBehavior(finding, behavior) {
|
|
815
|
+
if (behavior.ruleIds && !behavior.ruleIds.includes(finding.ruleId)) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
if (behavior.matchedPatterns && behavior.matchedPatterns.length > 0) {
|
|
819
|
+
const pattern = finding.evidence.matchedPattern ?? "";
|
|
820
|
+
if (!behavior.matchedPatterns.some((p) => pattern.includes(p))) {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
if (behavior.categories && behavior.categories.length > 0) {
|
|
825
|
+
if (!behavior.categories.includes(finding.category)) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
var DOUBLE_DOWNGRADE_MAP = {
|
|
832
|
+
critical: "low",
|
|
833
|
+
high: "info",
|
|
834
|
+
medium: "info",
|
|
835
|
+
low: "info",
|
|
836
|
+
info: "info"
|
|
837
|
+
};
|
|
838
|
+
function adjustFindings(findings, category, options = {}) {
|
|
839
|
+
const { publisher, extensionId, strictMode = false } = options;
|
|
840
|
+
const behaviors = EXPECTED_BEHAVIORS[category];
|
|
841
|
+
const isTrusted = !strictMode && (publisher && isTrustedPublisher(publisher) || extensionId && isTrustedExtension(extensionId));
|
|
842
|
+
const isVerified = !strictMode && publisher && isVerifiedPublisher(publisher);
|
|
843
|
+
const popularityTier = !strictMode && extensionId ? getPopularityTier(extensionId) : null;
|
|
844
|
+
return findings.map((finding) => {
|
|
845
|
+
let adjustedFinding = finding;
|
|
846
|
+
let reasons = [];
|
|
847
|
+
if (behaviors && behaviors.length > 0) {
|
|
848
|
+
for (const behavior of behaviors) {
|
|
849
|
+
if (matchesExpectedBehavior(finding, behavior)) {
|
|
850
|
+
const downgraded = DOWNGRADE_MAP[adjustedFinding.severity];
|
|
851
|
+
adjustedFinding = {
|
|
852
|
+
...adjustedFinding,
|
|
853
|
+
severity: downgraded
|
|
854
|
+
};
|
|
855
|
+
reasons.push(`expected behavior for ${category} extension`);
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (isTrusted && adjustedFinding.severity !== "info") {
|
|
861
|
+
const downgraded = DOWNGRADE_MAP[adjustedFinding.severity];
|
|
862
|
+
adjustedFinding = {
|
|
863
|
+
...adjustedFinding,
|
|
864
|
+
severity: downgraded
|
|
865
|
+
};
|
|
866
|
+
reasons.push(`trusted publisher`);
|
|
867
|
+
}
|
|
868
|
+
if (!isTrusted && isVerified && adjustedFinding.severity !== "info") {
|
|
869
|
+
const downgraded = DOWNGRADE_MAP[adjustedFinding.severity];
|
|
870
|
+
adjustedFinding = {
|
|
871
|
+
...adjustedFinding,
|
|
872
|
+
severity: downgraded
|
|
873
|
+
};
|
|
874
|
+
reasons.push(`verified publisher`);
|
|
875
|
+
}
|
|
876
|
+
if (popularityTier && adjustedFinding.severity !== "info") {
|
|
877
|
+
if (popularityTier === "mega") {
|
|
878
|
+
const downgraded = DOUBLE_DOWNGRADE_MAP[adjustedFinding.severity];
|
|
879
|
+
adjustedFinding = {
|
|
880
|
+
...adjustedFinding,
|
|
881
|
+
severity: downgraded
|
|
882
|
+
};
|
|
883
|
+
reasons.push(`mega popular (10M+ downloads)`);
|
|
884
|
+
} else if (popularityTier === "popular") {
|
|
885
|
+
const downgraded = DOWNGRADE_MAP[adjustedFinding.severity];
|
|
886
|
+
adjustedFinding = {
|
|
887
|
+
...adjustedFinding,
|
|
888
|
+
severity: downgraded
|
|
889
|
+
};
|
|
890
|
+
reasons.push(`popular (1M+ downloads)`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (reasons.length > 0) {
|
|
894
|
+
adjustedFinding = {
|
|
895
|
+
...adjustedFinding,
|
|
896
|
+
description: `${finding.description} [Downgraded: ${reasons.join(" + ")}]`
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
return adjustedFinding;
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
307
903
|
// src/rules/built-in/crit-data-exfiltration.ts
|
|
308
904
|
var SYSTEM_INFO_PATTERNS = [
|
|
309
905
|
{ name: "os.hostname", pattern: /os\.hostname\s*\(\)/g },
|
|
@@ -365,8 +961,14 @@ var critDataExfiltration = {
|
|
|
365
961
|
var DANGEROUS_PATTERNS = [
|
|
366
962
|
{ name: "eval", pattern: /\beval\s*\(/g },
|
|
367
963
|
{ name: "Function-constructor", pattern: /new\s+Function\s*\(/g },
|
|
368
|
-
{
|
|
369
|
-
|
|
964
|
+
{
|
|
965
|
+
name: "child_process-exec",
|
|
966
|
+
pattern: /(?:require\s*\(\s*['"]child_process['"]\s*\)|child_process)\.exec\s*\(/g
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
name: "child_process-execSync",
|
|
970
|
+
pattern: /(?:require\s*\(\s*['"]child_process['"]\s*\)|child_process)\.execSync\s*\(/g
|
|
971
|
+
},
|
|
370
972
|
{ name: "child_process-spawn-shell", pattern: /\.spawn\s*\([^)]*\{[^}]*shell\s*:\s*true/g },
|
|
371
973
|
{ name: "vm-runInContext", pattern: /vm\.run(?:InContext|InNewContext|InThisContext)\s*\(/g },
|
|
372
974
|
{ name: "vm-Script", pattern: /new\s+vm\.Script\s*\(/g }
|
|
@@ -420,7 +1022,10 @@ var critRemoteExecution = {
|
|
|
420
1022
|
|
|
421
1023
|
// src/rules/built-in/crit-credential-access.ts
|
|
422
1024
|
var SENSITIVE_PATHS = [
|
|
423
|
-
{
|
|
1025
|
+
{
|
|
1026
|
+
name: "ssh-keys",
|
|
1027
|
+
pattern: /['"`][^'"`]*\.ssh[/\\](?:id_rsa|id_ed25519|id_ecdsa|known_hosts|config|authorized_keys)[^'"`]*['"`]/gi
|
|
1028
|
+
},
|
|
424
1029
|
{ name: "gnupg", pattern: /['"`][^'"`]*\.gnupg[/\\][^'"`]*['"`]/gi },
|
|
425
1030
|
{ name: "aws-credentials", pattern: /['"`][^'"`]*\.aws[/\\]credentials[^'"`]*['"`]/gi },
|
|
426
1031
|
{ name: "azure-config", pattern: /['"`][^'"`]*\.azure[/\\][^'"`]*['"`]/gi },
|
|
@@ -522,6 +1127,8 @@ var BASE64_PATTERN = /['"`]([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{
|
|
|
522
1127
|
var HEX_PATTERN = /(?:\\x[0-9a-fA-F]{2}){10,}/g;
|
|
523
1128
|
var CHAR_CODE_PATTERN = /String\.fromCharCode\s*\(\s*(?:\d+\s*,?\s*){5,}\)/g;
|
|
524
1129
|
var UNICODE_ESCAPE_PATTERN = /(?:\\u[0-9a-fA-F]{4}){10,}/g;
|
|
1130
|
+
var BUNDLED_FILE_PATTERN = /(?:^|\/)(?:dist|out|build|bundle)\//;
|
|
1131
|
+
var BUNDLED_FILE_SIZE_THRESHOLD = 100 * 1024;
|
|
525
1132
|
function calculateEntropy(str) {
|
|
526
1133
|
const len = str.length;
|
|
527
1134
|
if (len === 0) return 0;
|
|
@@ -599,14 +1206,15 @@ var highObfuscatedCode = {
|
|
|
599
1206
|
snippet: match[0].slice(0, 50) + "..."
|
|
600
1207
|
});
|
|
601
1208
|
}
|
|
602
|
-
|
|
1209
|
+
const isBundled = BUNDLED_FILE_PATTERN.test(filePath) && content.length > BUNDLED_FILE_SIZE_THRESHOLD;
|
|
1210
|
+
if (!isBundled && content.length > 5e3) {
|
|
603
1211
|
const entropy = calculateEntropy(content);
|
|
604
|
-
if (entropy >
|
|
1212
|
+
if (entropy > 6.2) {
|
|
605
1213
|
evidences.push({
|
|
606
1214
|
filePath,
|
|
607
1215
|
lineNumber: 1,
|
|
608
1216
|
matchedPattern: "high-entropy",
|
|
609
|
-
snippet: `File entropy: ${entropy.toFixed(2)} (threshold:
|
|
1217
|
+
snippet: `File entropy: ${entropy.toFixed(2)} (threshold: 6.2)`
|
|
610
1218
|
});
|
|
611
1219
|
}
|
|
612
1220
|
}
|
|
@@ -617,6 +1225,7 @@ var highObfuscatedCode = {
|
|
|
617
1225
|
|
|
618
1226
|
// src/rules/built-in/high-hardcoded-secret.ts
|
|
619
1227
|
var MIN_SECRET_LENGTH = 8;
|
|
1228
|
+
var MIN_GENERIC_SECRET_ENTROPY = 3;
|
|
620
1229
|
var PLACEHOLDER_PATTERNS = [
|
|
621
1230
|
/^your[_-]?/i,
|
|
622
1231
|
/^<[^>]+>$/,
|
|
@@ -633,6 +1242,22 @@ var PLACEHOLDER_PATTERNS = [
|
|
|
633
1242
|
/^test$/i,
|
|
634
1243
|
/^demo$/i
|
|
635
1244
|
];
|
|
1245
|
+
var FALSE_POSITIVE_VALUES = [
|
|
1246
|
+
/^(?:true|false|null|undefined|none)$/i,
|
|
1247
|
+
/^(?:string|number|boolean|object|array|function)$/i,
|
|
1248
|
+
/^(?:keyword|identifier|operator|punctuation|comment|variable|constant)$/i,
|
|
1249
|
+
// TextMate token types
|
|
1250
|
+
/^(?:bearer|basic|digest)\s/i,
|
|
1251
|
+
// Auth scheme names, not actual tokens
|
|
1252
|
+
/^(?:process\.env|os\.environ)/i,
|
|
1253
|
+
// Env access patterns
|
|
1254
|
+
/^https?:\/\//i,
|
|
1255
|
+
// URLs
|
|
1256
|
+
/^\$\{/,
|
|
1257
|
+
// Template literals
|
|
1258
|
+
/^%[sd]/
|
|
1259
|
+
// Format strings
|
|
1260
|
+
];
|
|
636
1261
|
var EXCLUDED_FILE_PATTERNS = [
|
|
637
1262
|
/\.test\.[jt]sx?$/,
|
|
638
1263
|
/\.spec\.[jt]sx?$/,
|
|
@@ -701,6 +1326,23 @@ function isExcludedFile(filePath) {
|
|
|
701
1326
|
function isPlaceholder(value) {
|
|
702
1327
|
return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));
|
|
703
1328
|
}
|
|
1329
|
+
function isFalsePositiveValue(value) {
|
|
1330
|
+
return FALSE_POSITIVE_VALUES.some((pattern) => pattern.test(value));
|
|
1331
|
+
}
|
|
1332
|
+
function calculateSecretEntropy(str) {
|
|
1333
|
+
const len = str.length;
|
|
1334
|
+
if (len === 0) return 0;
|
|
1335
|
+
const freq = {};
|
|
1336
|
+
for (const char of str) {
|
|
1337
|
+
freq[char] = (freq[char] || 0) + 1;
|
|
1338
|
+
}
|
|
1339
|
+
let entropy = 0;
|
|
1340
|
+
for (const count of Object.values(freq)) {
|
|
1341
|
+
const p = count / len;
|
|
1342
|
+
entropy -= p * Math.log2(p);
|
|
1343
|
+
}
|
|
1344
|
+
return entropy;
|
|
1345
|
+
}
|
|
704
1346
|
function isInComment(content, matchIndex) {
|
|
705
1347
|
const lineStart = content.lastIndexOf("\n", matchIndex) + 1;
|
|
706
1348
|
const lineContent = content.slice(lineStart, matchIndex);
|
|
@@ -757,6 +1399,15 @@ var highHardcodedSecret = {
|
|
|
757
1399
|
if (isPlaceholder(secretValue)) {
|
|
758
1400
|
continue;
|
|
759
1401
|
}
|
|
1402
|
+
if (isFalsePositiveValue(secretValue)) {
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
if (secretPattern.name === "generic-secret") {
|
|
1406
|
+
const entropy = calculateSecretEntropy(secretValue);
|
|
1407
|
+
if (entropy < MIN_GENERIC_SECRET_ENTROPY) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
760
1411
|
const lineNumber = getLineNumber(content, matchIndex);
|
|
761
1412
|
const lineContent = lines[lineNumber - 1]?.trim() || "";
|
|
762
1413
|
evidences.push({
|
|
@@ -816,6 +1467,495 @@ function registerBuiltInRules() {
|
|
|
816
1467
|
ruleRegistry.register(highHardcodedSecret);
|
|
817
1468
|
ruleRegistry.register(medExcessiveActivation);
|
|
818
1469
|
}
|
|
1470
|
+
var DETECTION_RULES = [
|
|
1471
|
+
critDataExfiltration,
|
|
1472
|
+
critRemoteExecution,
|
|
1473
|
+
critCredentialAccess,
|
|
1474
|
+
highSuspiciousNetwork,
|
|
1475
|
+
highObfuscatedCode,
|
|
1476
|
+
highHardcodedSecret,
|
|
1477
|
+
medExcessiveActivation
|
|
1478
|
+
];
|
|
1479
|
+
|
|
1480
|
+
// src/scanner/extension-categorizer.ts
|
|
1481
|
+
var LANGUAGE_SUPPORT_PUBLISHERS = [
|
|
1482
|
+
"ms-python",
|
|
1483
|
+
"ms-vscode",
|
|
1484
|
+
"golang",
|
|
1485
|
+
"rust-lang",
|
|
1486
|
+
"microsoft",
|
|
1487
|
+
"redhat",
|
|
1488
|
+
"oracle",
|
|
1489
|
+
"julialang",
|
|
1490
|
+
"haskell",
|
|
1491
|
+
// Additional language support publishers
|
|
1492
|
+
"zigtools",
|
|
1493
|
+
"elixir-lsp",
|
|
1494
|
+
"dart-code",
|
|
1495
|
+
"scala-lang",
|
|
1496
|
+
"vscjava",
|
|
1497
|
+
"crystal-lang-tools",
|
|
1498
|
+
"nim-lang",
|
|
1499
|
+
"vlang-vscode"
|
|
1500
|
+
];
|
|
1501
|
+
var DEVELOPER_TOOL_KEYWORDS = [
|
|
1502
|
+
// Code execution
|
|
1503
|
+
"code runner",
|
|
1504
|
+
"coderunner",
|
|
1505
|
+
"run code",
|
|
1506
|
+
"execute code",
|
|
1507
|
+
"run script",
|
|
1508
|
+
"script runner",
|
|
1509
|
+
// REST/API clients
|
|
1510
|
+
"rest client",
|
|
1511
|
+
"api client",
|
|
1512
|
+
"http client",
|
|
1513
|
+
"thunder client",
|
|
1514
|
+
"postman",
|
|
1515
|
+
"insomnia",
|
|
1516
|
+
"api tester",
|
|
1517
|
+
"http request",
|
|
1518
|
+
// Live servers
|
|
1519
|
+
"live server",
|
|
1520
|
+
"liveserver",
|
|
1521
|
+
"live preview",
|
|
1522
|
+
"browser sync",
|
|
1523
|
+
"browsersync",
|
|
1524
|
+
"local server",
|
|
1525
|
+
"dev server",
|
|
1526
|
+
"http server",
|
|
1527
|
+
// Terminal/Shell
|
|
1528
|
+
"terminal",
|
|
1529
|
+
"shell",
|
|
1530
|
+
"integrated terminal",
|
|
1531
|
+
// Task runners
|
|
1532
|
+
"task runner",
|
|
1533
|
+
"npm scripts",
|
|
1534
|
+
"gulp",
|
|
1535
|
+
"grunt",
|
|
1536
|
+
// Database clients
|
|
1537
|
+
"database client",
|
|
1538
|
+
"sql client",
|
|
1539
|
+
"mongodb client",
|
|
1540
|
+
"redis client"
|
|
1541
|
+
];
|
|
1542
|
+
var DEVELOPER_TOOL_PUBLISHERS = [
|
|
1543
|
+
"formulahendry",
|
|
1544
|
+
// Code Runner
|
|
1545
|
+
"rangav",
|
|
1546
|
+
// Thunder Client
|
|
1547
|
+
"humao",
|
|
1548
|
+
// REST Client
|
|
1549
|
+
"ritwickdey",
|
|
1550
|
+
// Live Server
|
|
1551
|
+
"techer",
|
|
1552
|
+
// Live Server (alternative)
|
|
1553
|
+
"negokaz",
|
|
1554
|
+
// Live Server (alternative)
|
|
1555
|
+
"qwtel",
|
|
1556
|
+
// HTTP/S Server
|
|
1557
|
+
"hediet",
|
|
1558
|
+
// Debug Visualizer
|
|
1559
|
+
"mtxr",
|
|
1560
|
+
// SQLTools
|
|
1561
|
+
"cweijan"
|
|
1562
|
+
// Database Client
|
|
1563
|
+
];
|
|
1564
|
+
var REMOTE_DEV_KEYWORDS = [
|
|
1565
|
+
"remote-ssh",
|
|
1566
|
+
"remote ssh",
|
|
1567
|
+
"remote - ssh",
|
|
1568
|
+
"dev container",
|
|
1569
|
+
"devcontainer",
|
|
1570
|
+
"dev-container",
|
|
1571
|
+
"docker container",
|
|
1572
|
+
"wsl",
|
|
1573
|
+
"windows subsystem",
|
|
1574
|
+
"remote development",
|
|
1575
|
+
"remote explorer",
|
|
1576
|
+
"ssh remote",
|
|
1577
|
+
"ssh connection",
|
|
1578
|
+
"container development",
|
|
1579
|
+
"codespace",
|
|
1580
|
+
"github codespaces",
|
|
1581
|
+
"tunnel",
|
|
1582
|
+
"remote tunnels"
|
|
1583
|
+
];
|
|
1584
|
+
var REMOTE_DEV_PUBLISHERS = [
|
|
1585
|
+
"ms-vscode-remote",
|
|
1586
|
+
// Remote-SSH, Dev Containers, WSL
|
|
1587
|
+
"ms-azuretools"
|
|
1588
|
+
// Azure (containers, etc.)
|
|
1589
|
+
];
|
|
1590
|
+
var TESTING_KEYWORDS = [
|
|
1591
|
+
"test runner",
|
|
1592
|
+
"test explorer",
|
|
1593
|
+
"test adapter",
|
|
1594
|
+
"jest runner",
|
|
1595
|
+
"mocha test",
|
|
1596
|
+
"pytest",
|
|
1597
|
+
"vitest",
|
|
1598
|
+
"karma",
|
|
1599
|
+
"jasmine",
|
|
1600
|
+
"cypress",
|
|
1601
|
+
"playwright",
|
|
1602
|
+
"selenium",
|
|
1603
|
+
"unit test",
|
|
1604
|
+
"integration test",
|
|
1605
|
+
"e2e test",
|
|
1606
|
+
"end-to-end test",
|
|
1607
|
+
"test coverage",
|
|
1608
|
+
"code coverage"
|
|
1609
|
+
];
|
|
1610
|
+
var TESTING_PUBLISHERS = [
|
|
1611
|
+
"hbenl",
|
|
1612
|
+
// Test Explorer UI
|
|
1613
|
+
"firsttris",
|
|
1614
|
+
// Jest Runner
|
|
1615
|
+
"orta",
|
|
1616
|
+
// Jest (vscode-jest)
|
|
1617
|
+
"ms-playwright"
|
|
1618
|
+
// Playwright
|
|
1619
|
+
];
|
|
1620
|
+
var NOTEBOOK_KEYWORDS = [
|
|
1621
|
+
"jupyter",
|
|
1622
|
+
"notebook",
|
|
1623
|
+
"ipynb",
|
|
1624
|
+
"kernel",
|
|
1625
|
+
"jupyter notebook",
|
|
1626
|
+
"jupyter lab",
|
|
1627
|
+
"ipython",
|
|
1628
|
+
"data science",
|
|
1629
|
+
"interactive python"
|
|
1630
|
+
];
|
|
1631
|
+
var NOTEBOOK_PUBLISHERS = [
|
|
1632
|
+
"ms-toolsai"
|
|
1633
|
+
// Jupyter
|
|
1634
|
+
];
|
|
1635
|
+
var AI_KEYWORDS = [
|
|
1636
|
+
"copilot",
|
|
1637
|
+
"ai",
|
|
1638
|
+
"gpt",
|
|
1639
|
+
"llm",
|
|
1640
|
+
"chatbot",
|
|
1641
|
+
"assistant",
|
|
1642
|
+
"autocomplete",
|
|
1643
|
+
"code completion",
|
|
1644
|
+
"machine learning",
|
|
1645
|
+
"neural",
|
|
1646
|
+
"openai",
|
|
1647
|
+
"anthropic",
|
|
1648
|
+
"gemini",
|
|
1649
|
+
"claude",
|
|
1650
|
+
"codeium",
|
|
1651
|
+
"tabnine",
|
|
1652
|
+
"kilo",
|
|
1653
|
+
"continue",
|
|
1654
|
+
"cursor",
|
|
1655
|
+
"supermaven",
|
|
1656
|
+
// Additional AI tools
|
|
1657
|
+
"aider",
|
|
1658
|
+
"codestral",
|
|
1659
|
+
"mistral",
|
|
1660
|
+
"deepseek",
|
|
1661
|
+
"qwen",
|
|
1662
|
+
"sourcery",
|
|
1663
|
+
"codewhisperer",
|
|
1664
|
+
"amazon q",
|
|
1665
|
+
"bito",
|
|
1666
|
+
"blackbox",
|
|
1667
|
+
"codegpt",
|
|
1668
|
+
"cody"
|
|
1669
|
+
// Sourcegraph
|
|
1670
|
+
];
|
|
1671
|
+
function categorizeExtension(manifest) {
|
|
1672
|
+
const categories = (manifest.categories ?? []).map((c) => c.toLowerCase());
|
|
1673
|
+
const contributes = manifest.contributes ?? {};
|
|
1674
|
+
const contributeKeys = Object.keys(contributes);
|
|
1675
|
+
const keywords = manifest.keywords ?? [];
|
|
1676
|
+
const displayName = (manifest.displayName ?? "").toLowerCase();
|
|
1677
|
+
const description = (manifest.description ?? "").toLowerCase();
|
|
1678
|
+
const name = (manifest.name ?? "").toLowerCase();
|
|
1679
|
+
if (categories.includes("themes") || categories.includes("icon themes") || isThemeOnlyExtension(contributeKeys, contributes)) {
|
|
1680
|
+
return "theme";
|
|
1681
|
+
}
|
|
1682
|
+
if (isLanguageExtension(contributeKeys, contributes)) {
|
|
1683
|
+
return "language";
|
|
1684
|
+
}
|
|
1685
|
+
if (isRemoteDevelopment(manifest, displayName, description, name)) {
|
|
1686
|
+
return "remote-development";
|
|
1687
|
+
}
|
|
1688
|
+
if (isAIAssistant(categories, keywords, displayName, description, name)) {
|
|
1689
|
+
return "ai-assistant";
|
|
1690
|
+
}
|
|
1691
|
+
if (isLanguageSupport(manifest, categories)) {
|
|
1692
|
+
return "language-support";
|
|
1693
|
+
}
|
|
1694
|
+
if (categories.includes("scm providers") || contributeKeys.includes("scm")) {
|
|
1695
|
+
return "scm";
|
|
1696
|
+
}
|
|
1697
|
+
if (categories.includes("debuggers") || contributeKeys.includes("debuggers")) {
|
|
1698
|
+
return "debugger";
|
|
1699
|
+
}
|
|
1700
|
+
if (categories.includes("linters") || categories.includes("formatters") || displayName.includes("lint") || displayName.includes("format") || displayName.includes("prettier") || displayName.includes("eslint")) {
|
|
1701
|
+
return "linter";
|
|
1702
|
+
}
|
|
1703
|
+
if (isDeveloperTool(manifest, displayName, description, name)) {
|
|
1704
|
+
return "developer-tools";
|
|
1705
|
+
}
|
|
1706
|
+
if (isTesting(manifest, categories, displayName, description, name)) {
|
|
1707
|
+
return "testing";
|
|
1708
|
+
}
|
|
1709
|
+
if (isNotebook(manifest, categories, displayName, description, name, contributeKeys)) {
|
|
1710
|
+
return "notebook";
|
|
1711
|
+
}
|
|
1712
|
+
return "general";
|
|
1713
|
+
}
|
|
1714
|
+
function isThemeOnlyExtension(contributeKeys, _contributes) {
|
|
1715
|
+
const hasThemes = contributeKeys.includes("themes") || contributeKeys.includes("iconThemes");
|
|
1716
|
+
if (!hasThemes) return false;
|
|
1717
|
+
const functionalKeys = contributeKeys.filter(
|
|
1718
|
+
(k) => k !== "themes" && k !== "iconThemes" && k !== "colors"
|
|
1719
|
+
);
|
|
1720
|
+
return functionalKeys.length === 0;
|
|
1721
|
+
}
|
|
1722
|
+
function isLanguageExtension(contributeKeys, contributes) {
|
|
1723
|
+
const hasGrammars = contributeKeys.includes("grammars") || contributeKeys.includes("languages");
|
|
1724
|
+
if (!hasGrammars) return false;
|
|
1725
|
+
const commands = contributes.commands;
|
|
1726
|
+
if (Array.isArray(commands) && commands.length > 3) {
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
return true;
|
|
1730
|
+
}
|
|
1731
|
+
function isAIAssistant(categories, keywords, displayName, description, name) {
|
|
1732
|
+
if (categories.includes("machine learning") || categories.includes("data science")) {
|
|
1733
|
+
return true;
|
|
1734
|
+
}
|
|
1735
|
+
const searchableText = [
|
|
1736
|
+
...keywords.map((k) => k.toLowerCase()),
|
|
1737
|
+
displayName,
|
|
1738
|
+
description,
|
|
1739
|
+
name
|
|
1740
|
+
].join(" ");
|
|
1741
|
+
return AI_KEYWORDS.some((kw) => {
|
|
1742
|
+
if (kw.includes(" ")) {
|
|
1743
|
+
return searchableText.includes(kw);
|
|
1744
|
+
}
|
|
1745
|
+
const regex = new RegExp(`\\b${kw}\\b`, "i");
|
|
1746
|
+
return regex.test(searchableText);
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
function isLanguageSupport(manifest, categories) {
|
|
1750
|
+
const publisher = (manifest.publisher ?? "").toLowerCase();
|
|
1751
|
+
if (LANGUAGE_SUPPORT_PUBLISHERS.some((p) => publisher.includes(p))) {
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
if (categories.includes("programming languages")) {
|
|
1755
|
+
return true;
|
|
1756
|
+
}
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
function isDeveloperTool(manifest, displayName, description, name) {
|
|
1760
|
+
const publisher = (manifest.publisher ?? "").toLowerCase();
|
|
1761
|
+
if (DEVELOPER_TOOL_PUBLISHERS.some((p) => publisher === p)) {
|
|
1762
|
+
return true;
|
|
1763
|
+
}
|
|
1764
|
+
const searchableText = [displayName, description, name].join(" ");
|
|
1765
|
+
return DEVELOPER_TOOL_KEYWORDS.some((kw) => searchableText.includes(kw));
|
|
1766
|
+
}
|
|
1767
|
+
function isRemoteDevelopment(manifest, displayName, description, name) {
|
|
1768
|
+
const publisher = (manifest.publisher ?? "").toLowerCase();
|
|
1769
|
+
if (REMOTE_DEV_PUBLISHERS.some((p) => publisher === p)) {
|
|
1770
|
+
return true;
|
|
1771
|
+
}
|
|
1772
|
+
const searchableText = [displayName, description, name].join(" ");
|
|
1773
|
+
return REMOTE_DEV_KEYWORDS.some((kw) => searchableText.includes(kw));
|
|
1774
|
+
}
|
|
1775
|
+
function isTesting(manifest, categories, displayName, description, name) {
|
|
1776
|
+
const publisher = (manifest.publisher ?? "").toLowerCase();
|
|
1777
|
+
if (TESTING_PUBLISHERS.some((p) => publisher === p)) {
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
if (categories.includes("testing")) {
|
|
1781
|
+
return true;
|
|
1782
|
+
}
|
|
1783
|
+
const searchableText = [displayName, description, name].join(" ");
|
|
1784
|
+
return TESTING_KEYWORDS.some((kw) => searchableText.includes(kw));
|
|
1785
|
+
}
|
|
1786
|
+
function isNotebook(manifest, categories, displayName, description, name, contributeKeys) {
|
|
1787
|
+
const publisher = (manifest.publisher ?? "").toLowerCase();
|
|
1788
|
+
if (NOTEBOOK_PUBLISHERS.some((p) => publisher === p)) {
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
if (categories.includes("notebooks")) {
|
|
1792
|
+
return true;
|
|
1793
|
+
}
|
|
1794
|
+
if (contributeKeys.includes("notebooks") || contributeKeys.includes("notebookRenderer")) {
|
|
1795
|
+
return true;
|
|
1796
|
+
}
|
|
1797
|
+
const searchableText = [displayName, description, name].join(" ");
|
|
1798
|
+
return NOTEBOOK_KEYWORDS.some((kw) => searchableText.includes(kw));
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// src/integrity/integrity-verifier.ts
|
|
1802
|
+
import { createHash } from "crypto";
|
|
1803
|
+
function sha256(content) {
|
|
1804
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1805
|
+
}
|
|
1806
|
+
function computeExtensionHashes(extensionId, version, files) {
|
|
1807
|
+
const manifestContent = files.get("package.json") ?? "";
|
|
1808
|
+
const manifestHash = sha256(manifestContent);
|
|
1809
|
+
const jsFiles = Array.from(files.entries()).filter(([path6]) => path6.endsWith(".js") || path6.endsWith(".ts")).sort(([a], [b]) => a.localeCompare(b));
|
|
1810
|
+
const contentHash = sha256(jsFiles.map(([, content]) => content).join("\n"));
|
|
1811
|
+
const sortedPaths = Array.from(files.keys()).sort();
|
|
1812
|
+
const structureHash = sha256(sortedPaths.join("\n"));
|
|
1813
|
+
const combinedHash = sha256(`${manifestHash}:${contentHash}:${structureHash}`);
|
|
1814
|
+
return {
|
|
1815
|
+
extensionId,
|
|
1816
|
+
version,
|
|
1817
|
+
manifestHash,
|
|
1818
|
+
contentHash,
|
|
1819
|
+
structureHash,
|
|
1820
|
+
combinedHash
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function verifyIntegrity(extensionId, version, files, knownHashes) {
|
|
1824
|
+
try {
|
|
1825
|
+
const computed = computeExtensionHashes(extensionId, version, files);
|
|
1826
|
+
const key = `${extensionId}@${version}`;
|
|
1827
|
+
const expected = knownHashes.get(key);
|
|
1828
|
+
if (!expected) {
|
|
1829
|
+
return {
|
|
1830
|
+
extensionId,
|
|
1831
|
+
version,
|
|
1832
|
+
status: "unknown",
|
|
1833
|
+
computedHashes: {
|
|
1834
|
+
...computed
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
const manifestModified = computed.manifestHash !== expected.manifestHash;
|
|
1839
|
+
const contentModified = computed.contentHash !== expected.contentHash;
|
|
1840
|
+
const structureModified = computed.structureHash !== expected.structureHash;
|
|
1841
|
+
if (manifestModified || contentModified || structureModified) {
|
|
1842
|
+
return {
|
|
1843
|
+
extensionId,
|
|
1844
|
+
version,
|
|
1845
|
+
status: "modified",
|
|
1846
|
+
modifications: {
|
|
1847
|
+
manifest: manifestModified,
|
|
1848
|
+
content: contentModified,
|
|
1849
|
+
structure: structureModified
|
|
1850
|
+
},
|
|
1851
|
+
computedHashes: {
|
|
1852
|
+
...computed
|
|
1853
|
+
},
|
|
1854
|
+
expectedHashes: expected
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
return {
|
|
1858
|
+
extensionId,
|
|
1859
|
+
version,
|
|
1860
|
+
status: "verified",
|
|
1861
|
+
computedHashes: {
|
|
1862
|
+
...computed
|
|
1863
|
+
},
|
|
1864
|
+
expectedHashes: expected
|
|
1865
|
+
};
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
return {
|
|
1868
|
+
extensionId,
|
|
1869
|
+
version,
|
|
1870
|
+
status: "error",
|
|
1871
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function createHashRecord(extensionId, version, files, source = "manual") {
|
|
1876
|
+
const hashes = computeExtensionHashes(extensionId, version, files);
|
|
1877
|
+
return {
|
|
1878
|
+
...hashes,
|
|
1879
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1880
|
+
source
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// src/integrity/hash-database.ts
|
|
1885
|
+
import * as fs4 from "fs";
|
|
1886
|
+
import * as path4 from "path";
|
|
1887
|
+
var hashCache = null;
|
|
1888
|
+
function getDefaultDatabasePath() {
|
|
1889
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
1890
|
+
return path4.join(homeDir, ".extension-guard", "hash-database.json");
|
|
1891
|
+
}
|
|
1892
|
+
function loadHashDatabase(filePath) {
|
|
1893
|
+
const dbPath = filePath || getDefaultDatabasePath();
|
|
1894
|
+
if (hashCache && !filePath) {
|
|
1895
|
+
return hashCache;
|
|
1896
|
+
}
|
|
1897
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
1898
|
+
try {
|
|
1899
|
+
if (fs4.existsSync(dbPath)) {
|
|
1900
|
+
const content = fs4.readFileSync(dbPath, "utf8");
|
|
1901
|
+
const db = JSON.parse(content);
|
|
1902
|
+
for (const hash of db.hashes) {
|
|
1903
|
+
const key = `${hash.extensionId}@${hash.version}`;
|
|
1904
|
+
hashes.set(key, hash);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
} catch {
|
|
1908
|
+
}
|
|
1909
|
+
const bundled = getBundledHashes();
|
|
1910
|
+
for (const [key, hash] of bundled) {
|
|
1911
|
+
if (!hashes.has(key)) {
|
|
1912
|
+
hashes.set(key, hash);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (!filePath) {
|
|
1916
|
+
hashCache = hashes;
|
|
1917
|
+
}
|
|
1918
|
+
return hashes;
|
|
1919
|
+
}
|
|
1920
|
+
function saveHashDatabase(hashes, filePath) {
|
|
1921
|
+
const dbPath = filePath || getDefaultDatabasePath();
|
|
1922
|
+
const dir = path4.dirname(dbPath);
|
|
1923
|
+
if (!fs4.existsSync(dir)) {
|
|
1924
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1925
|
+
}
|
|
1926
|
+
const db = {
|
|
1927
|
+
version: "1.0",
|
|
1928
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1929
|
+
hashes: Array.from(hashes.values())
|
|
1930
|
+
};
|
|
1931
|
+
fs4.writeFileSync(dbPath, JSON.stringify(db, null, 2));
|
|
1932
|
+
if (!filePath) {
|
|
1933
|
+
hashCache = hashes;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
function addHash(hash, filePath) {
|
|
1937
|
+
const hashes = loadHashDatabase(filePath);
|
|
1938
|
+
const key = `${hash.extensionId}@${hash.version}`;
|
|
1939
|
+
hashes.set(key, hash);
|
|
1940
|
+
saveHashDatabase(hashes, filePath);
|
|
1941
|
+
}
|
|
1942
|
+
function getHash(extensionId, version, filePath) {
|
|
1943
|
+
const hashes = loadHashDatabase(filePath);
|
|
1944
|
+
return hashes.get(`${extensionId}@${version}`);
|
|
1945
|
+
}
|
|
1946
|
+
function clearHashCache() {
|
|
1947
|
+
hashCache = null;
|
|
1948
|
+
}
|
|
1949
|
+
function getBundledHashes() {
|
|
1950
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
1951
|
+
return hashes;
|
|
1952
|
+
}
|
|
1953
|
+
async function generateBaseline(_extensionPaths, _outputPath) {
|
|
1954
|
+
return {
|
|
1955
|
+
generated: 0,
|
|
1956
|
+
errors: ["Use CLI baseline command instead"]
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
819
1959
|
|
|
820
1960
|
// src/scanner/scanner.ts
|
|
821
1961
|
var rulesRegistered = false;
|
|
@@ -832,7 +1972,9 @@ var DEFAULT_OPTIONS = {
|
|
|
832
1972
|
rules: [],
|
|
833
1973
|
skipRules: [],
|
|
834
1974
|
concurrency: 4,
|
|
835
|
-
timeout: 3e4
|
|
1975
|
+
timeout: 3e4,
|
|
1976
|
+
verifyIntegrity: false,
|
|
1977
|
+
hashDatabasePath: ""
|
|
836
1978
|
};
|
|
837
1979
|
var SEVERITY_PENALTY = {
|
|
838
1980
|
critical: 35,
|
|
@@ -844,6 +1986,7 @@ var SEVERITY_PENALTY = {
|
|
|
844
1986
|
var ExtensionGuardScanner = class {
|
|
845
1987
|
options;
|
|
846
1988
|
ruleEngine;
|
|
1989
|
+
hashDatabase = null;
|
|
847
1990
|
constructor(options) {
|
|
848
1991
|
ensureRulesRegistered();
|
|
849
1992
|
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -852,6 +1995,11 @@ var ExtensionGuardScanner = class {
|
|
|
852
1995
|
skipRules: this.options.skipRules.length > 0 ? this.options.skipRules : void 0,
|
|
853
1996
|
minSeverity: this.options.severity
|
|
854
1997
|
});
|
|
1998
|
+
if (this.options.verifyIntegrity) {
|
|
1999
|
+
this.hashDatabase = loadHashDatabase(
|
|
2000
|
+
this.options.hashDatabasePath || void 0
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
855
2003
|
}
|
|
856
2004
|
async scan(options) {
|
|
857
2005
|
const startTime = Date.now();
|
|
@@ -915,9 +2063,41 @@ var ExtensionGuardScanner = class {
|
|
|
915
2063
|
} catch {
|
|
916
2064
|
}
|
|
917
2065
|
}
|
|
918
|
-
const
|
|
2066
|
+
const rawFindings = this.ruleEngine.run(files, manifest);
|
|
2067
|
+
const category = categorizeExtension(manifest);
|
|
2068
|
+
const findings = adjustFindings(rawFindings, category, {
|
|
2069
|
+
publisher: ext.publisher.name,
|
|
2070
|
+
extensionId: ext.id
|
|
2071
|
+
});
|
|
919
2072
|
const trustScore = this.calculateTrustScore(findings);
|
|
920
|
-
|
|
2073
|
+
let riskLevel = this.calculateRiskLevel(trustScore, findings);
|
|
2074
|
+
let integrity;
|
|
2075
|
+
if (this.options.verifyIntegrity && this.hashDatabase) {
|
|
2076
|
+
const result = verifyIntegrity(ext.id, ext.version, files, this.hashDatabase);
|
|
2077
|
+
integrity = {
|
|
2078
|
+
status: result.status,
|
|
2079
|
+
modifications: result.modifications,
|
|
2080
|
+
hash: result.computedHashes?.combinedHash
|
|
2081
|
+
};
|
|
2082
|
+
if (result.status === "modified") {
|
|
2083
|
+
riskLevel = "critical";
|
|
2084
|
+
findings.unshift({
|
|
2085
|
+
id: `integrity-${ext.id}`,
|
|
2086
|
+
ruleId: "EG-CRIT-100",
|
|
2087
|
+
severity: "critical",
|
|
2088
|
+
category: "supply-chain",
|
|
2089
|
+
title: "Extension Integrity Compromised",
|
|
2090
|
+
description: `Extension files have been modified from known-good version. Modifications: ${result.modifications?.manifest ? "manifest " : ""}${result.modifications?.content ? "content " : ""}${result.modifications?.structure ? "structure" : ""}`.trim(),
|
|
2091
|
+
evidence: {
|
|
2092
|
+
filePath: ext.installPath,
|
|
2093
|
+
matchedPattern: "integrity-violation"
|
|
2094
|
+
},
|
|
2095
|
+
remediation: "Reinstall the extension from the official marketplace. If this persists, report to the extension author."
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
} else if (this.options.verifyIntegrity) {
|
|
2099
|
+
integrity = { status: "skipped" };
|
|
2100
|
+
}
|
|
921
2101
|
return {
|
|
922
2102
|
extensionId: ext.id,
|
|
923
2103
|
displayName: ext.displayName,
|
|
@@ -927,21 +2107,30 @@ var ExtensionGuardScanner = class {
|
|
|
927
2107
|
findings,
|
|
928
2108
|
metadata: ext,
|
|
929
2109
|
analyzedFiles: files.size,
|
|
930
|
-
scanDurationMs: Date.now() - startTime
|
|
2110
|
+
scanDurationMs: Date.now() - startTime,
|
|
2111
|
+
integrity
|
|
931
2112
|
};
|
|
932
2113
|
}
|
|
933
2114
|
calculateTrustScore(findings) {
|
|
934
2115
|
let score = 100;
|
|
2116
|
+
const findingsByRule = /* @__PURE__ */ new Map();
|
|
935
2117
|
for (const finding of findings) {
|
|
936
|
-
|
|
2118
|
+
const count = findingsByRule.get(finding.ruleId) ?? 0;
|
|
2119
|
+
if (count < 5) {
|
|
2120
|
+
score -= SEVERITY_PENALTY[finding.severity];
|
|
2121
|
+
findingsByRule.set(finding.ruleId, count + 1);
|
|
2122
|
+
}
|
|
937
2123
|
}
|
|
938
2124
|
return Math.max(0, Math.min(100, score));
|
|
939
2125
|
}
|
|
940
2126
|
calculateRiskLevel(trustScore, findings) {
|
|
941
|
-
|
|
2127
|
+
const realFindings = findings.filter(
|
|
2128
|
+
(f) => !f.description?.includes("[Downgraded:")
|
|
2129
|
+
);
|
|
2130
|
+
if (realFindings.some((f) => f.severity === "critical")) {
|
|
942
2131
|
return "critical";
|
|
943
2132
|
}
|
|
944
|
-
if (
|
|
2133
|
+
if (realFindings.some((f) => f.severity === "high")) {
|
|
945
2134
|
return "high";
|
|
946
2135
|
}
|
|
947
2136
|
if (trustScore >= 90) return "safe";
|
|
@@ -995,6 +2184,129 @@ var ExtensionGuardScanner = class {
|
|
|
995
2184
|
}
|
|
996
2185
|
};
|
|
997
2186
|
|
|
2187
|
+
// src/analyzers/bundle-detector.ts
|
|
2188
|
+
var BUNDLER_SIGNATURES = {
|
|
2189
|
+
webpack: [
|
|
2190
|
+
/\/\*{3}\/ var __webpack_/,
|
|
2191
|
+
/\/\*! For license information /,
|
|
2192
|
+
/webpackChunk/,
|
|
2193
|
+
/__webpack_require__/,
|
|
2194
|
+
/\/\*\*\*\/ \(/
|
|
2195
|
+
],
|
|
2196
|
+
esbuild: [
|
|
2197
|
+
/\/\/ src\//,
|
|
2198
|
+
/__esm\s*\(/,
|
|
2199
|
+
/__export\s*\(/,
|
|
2200
|
+
/var __defProp\s*=/,
|
|
2201
|
+
/var __getOwnPropDesc\s*=/
|
|
2202
|
+
],
|
|
2203
|
+
rollup: [
|
|
2204
|
+
/\/\*\* @license/,
|
|
2205
|
+
/\(function \(global, factory\)/,
|
|
2206
|
+
/typeof exports === 'object'/,
|
|
2207
|
+
/define\(\['exports'/
|
|
2208
|
+
],
|
|
2209
|
+
parcel: [/parcelRequire/, /@parcel\/transformer/],
|
|
2210
|
+
vite: [/vite\/modulepreload-polyfill/, /import\.meta\.hot/],
|
|
2211
|
+
browserify: [/require=\(function e\(t,n,r\)/, /typeof require=="function"/]
|
|
2212
|
+
};
|
|
2213
|
+
var CHUNK_PATTERNS = [
|
|
2214
|
+
/\d+\.chunk\.js$/i,
|
|
2215
|
+
/chunk-[a-f0-9]+\.js$/i,
|
|
2216
|
+
/\.bundle\.js$/i,
|
|
2217
|
+
/\.min\.js$/i,
|
|
2218
|
+
/\.prod\.js$/i,
|
|
2219
|
+
/vendor[\.\-]/i,
|
|
2220
|
+
/runtime[\.\-][a-f0-9]+\.js$/i
|
|
2221
|
+
];
|
|
2222
|
+
function detectBundle(filePath, content) {
|
|
2223
|
+
const reasons = [];
|
|
2224
|
+
let detectedBundler;
|
|
2225
|
+
const filename = filePath.split("/").pop() || filePath;
|
|
2226
|
+
for (const pattern of CHUNK_PATTERNS) {
|
|
2227
|
+
if (pattern.test(filename)) {
|
|
2228
|
+
reasons.push(`Filename matches chunk pattern: ${filename}`);
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
for (const [bundler, patterns] of Object.entries(BUNDLER_SIGNATURES)) {
|
|
2233
|
+
for (const pattern of patterns) {
|
|
2234
|
+
if (pattern.test(content)) {
|
|
2235
|
+
reasons.push(`Contains ${bundler} signature`);
|
|
2236
|
+
detectedBundler = bundler;
|
|
2237
|
+
break;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
if (detectedBundler) break;
|
|
2241
|
+
}
|
|
2242
|
+
const lines = content.split("\n");
|
|
2243
|
+
const longLineCount = lines.filter((line) => line.length > 500).length;
|
|
2244
|
+
const totalLines = lines.length;
|
|
2245
|
+
if (totalLines > 0 && longLineCount / totalLines > 0.1) {
|
|
2246
|
+
reasons.push(`High percentage of long lines (${longLineCount}/${totalLines})`);
|
|
2247
|
+
}
|
|
2248
|
+
const totalChars = content.length;
|
|
2249
|
+
const avgLineLength = totalChars / Math.max(totalLines, 1);
|
|
2250
|
+
if (avgLineLength > 200) {
|
|
2251
|
+
reasons.push(`High average line length (${Math.round(avgLineLength)} chars)`);
|
|
2252
|
+
}
|
|
2253
|
+
if (/\/\/# sourceMappingURL=/.test(content)) {
|
|
2254
|
+
reasons.push("Contains source map reference");
|
|
2255
|
+
}
|
|
2256
|
+
if (/\(self\["webpackChunk/.test(content)) {
|
|
2257
|
+
reasons.push("Contains webpack chunk pattern");
|
|
2258
|
+
if (!detectedBundler) detectedBundler = "webpack";
|
|
2259
|
+
}
|
|
2260
|
+
const exportCount = (content.match(/export\s*\{|exports\./g) || []).length;
|
|
2261
|
+
if (exportCount > 20) {
|
|
2262
|
+
reasons.push(`Many export statements (${exportCount})`);
|
|
2263
|
+
}
|
|
2264
|
+
let confidence = "low";
|
|
2265
|
+
if (detectedBundler || reasons.length >= 3) {
|
|
2266
|
+
confidence = "high";
|
|
2267
|
+
} else if (reasons.length >= 2) {
|
|
2268
|
+
confidence = "medium";
|
|
2269
|
+
}
|
|
2270
|
+
return {
|
|
2271
|
+
isBundled: reasons.length > 0,
|
|
2272
|
+
confidence,
|
|
2273
|
+
reasons,
|
|
2274
|
+
bundler: detectedBundler
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
function isBundleOutputPath(filePath) {
|
|
2278
|
+
const bundleOutputDirs = [
|
|
2279
|
+
"/dist/",
|
|
2280
|
+
"/build/",
|
|
2281
|
+
"/out/",
|
|
2282
|
+
"/lib/",
|
|
2283
|
+
"/.output/",
|
|
2284
|
+
"/bundle/",
|
|
2285
|
+
"/packed/",
|
|
2286
|
+
"/compiled/"
|
|
2287
|
+
];
|
|
2288
|
+
const normalizedPath = filePath.toLowerCase();
|
|
2289
|
+
return bundleOutputDirs.some((dir) => normalizedPath.includes(dir));
|
|
2290
|
+
}
|
|
2291
|
+
function shouldReduceSeverityForBundle(filePath, content) {
|
|
2292
|
+
if (isBundleOutputPath(filePath)) {
|
|
2293
|
+
const result = detectBundle(filePath, content);
|
|
2294
|
+
if (result.isBundled && result.confidence !== "low") {
|
|
2295
|
+
return {
|
|
2296
|
+
reduce: true,
|
|
2297
|
+
reason: `Bundled file (${result.bundler || "detected"}, ${result.confidence} confidence)`
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
if (/\.(bundle|min|prod|chunk)\.js$/i.test(filePath)) {
|
|
2302
|
+
return {
|
|
2303
|
+
reduce: true,
|
|
2304
|
+
reason: "Bundle filename pattern"
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
return { reduce: false };
|
|
2308
|
+
}
|
|
2309
|
+
|
|
998
2310
|
// src/reporter/json-reporter.ts
|
|
999
2311
|
var SEVERITY_ORDER2 = {
|
|
1000
2312
|
critical: 0,
|
|
@@ -1006,11 +2318,7 @@ var SEVERITY_ORDER2 = {
|
|
|
1006
2318
|
var JsonReporter = class {
|
|
1007
2319
|
format = "json";
|
|
1008
2320
|
generate(report, options = {}) {
|
|
1009
|
-
const {
|
|
1010
|
-
includeEvidence = true,
|
|
1011
|
-
includeSafe = true,
|
|
1012
|
-
minSeverity = "info"
|
|
1013
|
-
} = options;
|
|
2321
|
+
const { includeEvidence = true, includeSafe = true, minSeverity = "info" } = options;
|
|
1014
2322
|
const filteredResults = this.filterResults(report.results, {
|
|
1015
2323
|
includeSafe,
|
|
1016
2324
|
minSeverity,
|
|
@@ -1279,13 +2587,13 @@ var MarkdownReporter = class {
|
|
|
1279
2587
|
};
|
|
1280
2588
|
|
|
1281
2589
|
// src/policy/policy-loader.ts
|
|
1282
|
-
import * as
|
|
1283
|
-
import * as
|
|
2590
|
+
import * as fs5 from "fs/promises";
|
|
2591
|
+
import * as path5 from "path";
|
|
1284
2592
|
var DEFAULT_CONFIG_NAME = ".extension-guard.json";
|
|
1285
2593
|
async function loadPolicyConfig(configPath) {
|
|
1286
|
-
const resolvedPath = configPath ??
|
|
2594
|
+
const resolvedPath = configPath ?? path5.join(process.cwd(), DEFAULT_CONFIG_NAME);
|
|
1287
2595
|
try {
|
|
1288
|
-
const content = await
|
|
2596
|
+
const content = await fs5.readFile(resolvedPath, "utf-8");
|
|
1289
2597
|
const config = JSON.parse(content);
|
|
1290
2598
|
validatePolicyConfig(config);
|
|
1291
2599
|
return config;
|
|
@@ -1539,27 +2847,56 @@ var PolicyEngine = class {
|
|
|
1539
2847
|
};
|
|
1540
2848
|
|
|
1541
2849
|
// src/index.ts
|
|
1542
|
-
var VERSION = "0.
|
|
2850
|
+
var VERSION = "0.4.0";
|
|
1543
2851
|
export {
|
|
2852
|
+
ALL_POPULAR_EXTENSIONS,
|
|
2853
|
+
DETECTION_RULES,
|
|
1544
2854
|
ExtensionGuardScanner,
|
|
1545
2855
|
IDE_PATHS,
|
|
1546
2856
|
JsonReporter,
|
|
2857
|
+
MEGA_POPULAR_EXTENSIONS,
|
|
1547
2858
|
MarkdownReporter,
|
|
2859
|
+
POPULAR_EXTENSIONS,
|
|
1548
2860
|
PolicyEngine,
|
|
1549
2861
|
RuleEngine,
|
|
1550
2862
|
SEVERITY_ORDER,
|
|
1551
2863
|
SarifReporter,
|
|
2864
|
+
TRUSTED_EXTENSION_IDS,
|
|
2865
|
+
TRUSTED_PUBLISHERS,
|
|
2866
|
+
VERIFIED_PUBLISHERS,
|
|
1552
2867
|
VERSION,
|
|
2868
|
+
addHash,
|
|
2869
|
+
adjustFindings,
|
|
2870
|
+
categorizeExtension,
|
|
2871
|
+
clearHashCache,
|
|
1553
2872
|
collectFiles,
|
|
1554
2873
|
compareSeverity,
|
|
2874
|
+
computeExtensionHashes,
|
|
2875
|
+
createHashRecord,
|
|
2876
|
+
detectBundle,
|
|
1555
2877
|
detectIDEPaths,
|
|
1556
2878
|
expandPath,
|
|
2879
|
+
generateBaseline,
|
|
2880
|
+
getDefaultDatabasePath,
|
|
2881
|
+
getHash,
|
|
2882
|
+
getPopularityTier,
|
|
1557
2883
|
isAtLeastSeverity,
|
|
2884
|
+
isBundleOutputPath,
|
|
2885
|
+
isMegaPopular,
|
|
2886
|
+
isPopular,
|
|
2887
|
+
isTrustedExtension,
|
|
2888
|
+
isTrustedPublisher,
|
|
2889
|
+
isVerifiedPublisher,
|
|
2890
|
+
loadHashDatabase,
|
|
1558
2891
|
loadPolicyConfig,
|
|
1559
2892
|
readExtension,
|
|
1560
2893
|
readExtensionsFromDirectory,
|
|
1561
2894
|
registerBuiltInRules,
|
|
1562
2895
|
ruleRegistry,
|
|
1563
|
-
|
|
2896
|
+
saveHashDatabase,
|
|
2897
|
+
sha256,
|
|
2898
|
+
shouldCollectFile,
|
|
2899
|
+
shouldReduceSeverityForBundle,
|
|
2900
|
+
verifyIntegrity
|
|
1564
2901
|
};
|
|
1565
2902
|
//# sourceMappingURL=index.js.map
|