@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.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
- ".js",
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
- { name: "child_process-exec", pattern: /(?:require\s*\(\s*['"]child_process['"]\s*\)|child_process)\.exec\s*\(/g },
369
- { name: "child_process-execSync", pattern: /(?:require\s*\(\s*['"]child_process['"]\s*\)|child_process)\.execSync\s*\(/g },
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
- { name: "ssh-keys", pattern: /['"`][^'"`]*\.ssh[/\\](?:id_rsa|id_ed25519|id_ecdsa|known_hosts|config|authorized_keys)[^'"`]*['"`]/gi },
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
- if (content.length > 5e3) {
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 > 5.8) {
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: 5.8)`
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 findings = this.ruleEngine.run(files, manifest);
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
- const riskLevel = this.calculateRiskLevel(trustScore, findings);
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
- score -= SEVERITY_PENALTY[finding.severity];
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
- if (findings.some((f) => f.severity === "critical")) {
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 (findings.some((f) => f.severity === "high")) {
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 fs4 from "fs/promises";
1283
- import * as path4 from "path";
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 ?? path4.join(process.cwd(), DEFAULT_CONFIG_NAME);
2594
+ const resolvedPath = configPath ?? path5.join(process.cwd(), DEFAULT_CONFIG_NAME);
1287
2595
  try {
1288
- const content = await fs4.readFile(resolvedPath, "utf-8");
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.1.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
- shouldCollectFile
2896
+ saveHashDatabase,
2897
+ sha256,
2898
+ shouldCollectFile,
2899
+ shouldReduceSeverityForBundle,
2900
+ verifyIntegrity
1564
2901
  };
1565
2902
  //# sourceMappingURL=index.js.map