legacy-squad 1.0.0-beta.5 → 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.mjs +209 -10
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -363,11 +363,23 @@ var composerJsonDetector = {
363
363
  { name: "php", type: "language", version: composer.require?.php ?? "unknown", source: filePath }
364
364
  ];
365
365
  const dependencies = [];
366
+ const FRAMEWORK_MAP = [
367
+ { pkg: "laravel/framework", name: "laravel" },
368
+ { pkg: "symfony/framework-bundle", name: "symfony" },
369
+ { pkg: "symfony/symfony", name: "symfony" },
370
+ { pkg: "codeigniter4/framework", name: "codeigniter" },
371
+ { pkg: "codeigniter/framework", name: "codeigniter" }
372
+ ];
373
+ const allRequires = { ...composer.require ?? {}, ...composer["require-dev"] ?? {} };
366
374
  for (const [name, version] of Object.entries(composer.require ?? {})) {
367
375
  if (name === "php") continue;
368
376
  dependencies.push({ name, version: String(version), manager: "composer", scope: "runtime" });
369
- if (name === "laravel/framework") {
370
- stack.push({ name: "laravel", type: "framework", version: String(version), source: filePath });
377
+ }
378
+ const seenFrameworks = /* @__PURE__ */ new Set();
379
+ for (const { pkg, name } of FRAMEWORK_MAP) {
380
+ if (allRequires[pkg] && !seenFrameworks.has(name)) {
381
+ seenFrameworks.add(name);
382
+ stack.push({ name, type: "framework", version: String(allRequires[pkg]), source: filePath });
371
383
  }
372
384
  }
373
385
  return { stack, dependencies, projectType: "backend", projectName: composer.name ?? "php-project" };
@@ -380,12 +392,22 @@ var csprojDetector = {
380
392
  const dependencies = [];
381
393
  const tfmMatch = content.match(/<TargetFramework>(.*?)<\/TargetFramework>/);
382
394
  if (tfmMatch) {
383
- stack.push({ name: "dotnet", type: "runtime", version: tfmMatch[1], source: filePath });
395
+ const tfm = tfmMatch[1];
396
+ stack.push({ name: "dotnet", type: "runtime", version: tfm, source: filePath });
397
+ if (/^net4\d|^net35/.test(tfm)) {
398
+ stack.push({ name: ".net-framework", type: "runtime", version: tfm, source: filePath });
399
+ }
400
+ }
401
+ if (/Sdk\s*=\s*["']Microsoft\.NET\.Sdk\.Web["']/.test(content)) {
402
+ stack.push({ name: "asp.net", type: "framework", version: "detected", source: filePath });
384
403
  }
385
404
  const pkgRefRegex = /<PackageReference\s+Include="([^"]+)"\s+Version="([^"]+)"/g;
386
405
  let match;
387
406
  while ((match = pkgRefRegex.exec(content)) !== null) {
388
407
  dependencies.push({ name: match[1], version: match[2], manager: "nuget", scope: "runtime" });
408
+ if (match[1].startsWith("Microsoft.AspNetCore") && !stack.some((s) => s.name === "asp.net")) {
409
+ stack.push({ name: "asp.net", type: "framework", version: match[2], source: filePath });
410
+ }
389
411
  }
390
412
  return { stack, dependencies, projectType: "backend", projectName: path2.basename(filePath, ".csproj") };
391
413
  }
@@ -397,17 +419,34 @@ var pomXmlDetector = {
397
419
  { name: "java", type: "language", version: "unknown", source: filePath }
398
420
  ];
399
421
  const dependencies = [];
400
- if (content.includes("spring-boot")) {
422
+ if (/<artifactId>\s*spring-boot/i.test(content) || content.includes("spring-boot-starter")) {
401
423
  stack.push({ name: "spring-boot", type: "framework", version: "detected", source: filePath });
424
+ } else if (/<artifactId>\s*spring-webmvc\s*<\/artifactId>/i.test(content) || content.includes("spring-webmvc")) {
425
+ stack.push({ name: "spring-mvc", type: "framework", version: "detected", source: filePath });
402
426
  }
403
427
  return { stack, dependencies, projectType: "backend", projectName: "java-project" };
404
428
  }
405
429
  };
430
+ var gradleDetector = {
431
+ filename: "build.gradle",
432
+ detect(content, filePath) {
433
+ const stack = [
434
+ { name: "java", type: "language", version: "unknown", source: filePath }
435
+ ];
436
+ if (content.includes("org.springframework.boot") || content.includes("spring-boot-starter")) {
437
+ stack.push({ name: "spring-boot", type: "framework", version: "detected", source: filePath });
438
+ } else if (content.includes("spring-webmvc")) {
439
+ stack.push({ name: "spring-mvc", type: "framework", version: "detected", source: filePath });
440
+ }
441
+ return { stack, dependencies: [], projectType: "backend", projectName: "java-project" };
442
+ }
443
+ };
406
444
  var ALL_DETECTORS = [
407
445
  packageJsonDetector,
408
446
  composerJsonDetector,
409
447
  csprojDetector,
410
- pomXmlDetector
448
+ pomXmlDetector,
449
+ gradleDetector
411
450
  ];
412
451
  var MANIFEST_FILES = ALL_DETECTORS.map((d) => d.filename);
413
452
  async function detectFromManifests(rootPath, fs) {
@@ -689,14 +728,27 @@ var ContextBuilder = class {
689
728
  import path5 from "node:path";
690
729
 
691
730
  // packages/rules/src/rule-catalog.ts
731
+ var BACKEND_LANGUAGES = [
732
+ "backend",
733
+ "php",
734
+ "laravel",
735
+ "symfony",
736
+ "codeigniter",
737
+ "dotnet",
738
+ "csharp",
739
+ "asp.net",
740
+ "java",
741
+ "spring-boot",
742
+ "spring-mvc"
743
+ ];
692
744
  var SECURITY_RULES = [
693
745
  {
694
746
  id: "SEC-CRED-001",
695
747
  title: "Hardcoded credentials in source code",
696
748
  category: "security",
697
749
  severity: "critical",
698
- appliesTo: ["react-native", "node", "mobile", "backend", "frontend"],
699
- frameworks: ["OWASP MASVS V2", "CWE-798"],
750
+ appliesTo: ["react-native", "node", "mobile", "backend", "frontend", ...BACKEND_LANGUAGES],
751
+ frameworks: ["OWASP MASVS V2", "OWASP ASVS V2", "CWE-798"],
700
752
  detection: {
701
753
  type: "pattern",
702
754
  patterns: [
@@ -747,7 +799,7 @@ var SECURITY_RULES = [
747
799
  title: "Sensitive data (CPF/PII) logged or sent to external service",
748
800
  category: "security",
749
801
  severity: "high",
750
- appliesTo: ["react-native", "node", "mobile", "backend"],
802
+ appliesTo: ["react-native", "node", "mobile", "backend", ...BACKEND_LANGUAGES],
751
803
  frameworks: ["OWASP MASVS V2", "CWE-532", "LGPD"],
752
804
  detection: {
753
805
  type: "pattern",
@@ -765,7 +817,7 @@ var SECURITY_RULES = [
765
817
  title: "Empty catch block swallowing errors silently",
766
818
  category: "security",
767
819
  severity: "medium",
768
- appliesTo: ["react-native", "node", "mobile", "frontend", "backend"],
820
+ appliesTo: ["react-native", "node", "mobile", "frontend", "backend", ...BACKEND_LANGUAGES],
769
821
  frameworks: ["CWE-390", "Clean Code"],
770
822
  detection: {
771
823
  type: "pattern",
@@ -775,7 +827,7 @@ var SECURITY_RULES = [
775
827
  ]
776
828
  },
777
829
  impact: "Errors are silently discarded, masking bugs and security issues in production.",
778
- recommendation: "Log errors to a monitoring service (Sentry, Crashlytics). Never leave catch blocks empty."
830
+ recommendation: "Log errors to a monitoring service (Sentry, Crashlytics, Application Insights). Never leave catch blocks empty."
779
831
  },
780
832
  {
781
833
  id: "SEC-STORE-001",
@@ -792,6 +844,132 @@ var SECURITY_RULES = [
792
844
  },
793
845
  impact: "Authentication tokens in AsyncStorage are accessible to other apps on rooted devices.",
794
846
  recommendation: "Use expo-secure-store, react-native-keychain, or native Keychain/Keystore APIs."
847
+ },
848
+ // ─── Regras multi-linguagem para backend (DT-003) ──────────────────────────
849
+ {
850
+ id: "SEC-SQL-001",
851
+ title: "Possible SQL injection via string concatenation",
852
+ category: "security",
853
+ severity: "critical",
854
+ appliesTo: BACKEND_LANGUAGES,
855
+ frameworks: ["OWASP ASVS V5", "OWASP Top 10 A03:2021", "CWE-89"],
856
+ detection: {
857
+ type: "pattern",
858
+ patterns: [
859
+ // PHP: keyword SQL na mesma linha que superglobal
860
+ "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\$_(GET|POST|REQUEST|COOKIE)",
861
+ // .NET: SqlCommand/CommandText concatenando string
862
+ "(SqlCommand|CommandText)[^;{]*\\+\\s*\\w",
863
+ // Java: execute*/executeQuery/executeUpdate concatenando string
864
+ '(executeQuery|executeUpdate|execute)\\s*\\([^)]*"\\s*\\+'
865
+ ]
866
+ },
867
+ impact: "Attacker-controlled input concatenated into SQL allows arbitrary query execution, data exfiltration or database compromise.",
868
+ recommendation: "Use parameterized queries / prepared statements: PDO/mysqli in PHP (?-placeholders), SqlParameter in .NET, PreparedStatement in Java, or ORM bindings (Eloquent, EF Core, Hibernate) without raw concatenation."
869
+ },
870
+ {
871
+ id: "SEC-CRYPTO-001",
872
+ title: "Weak cryptographic hash (MD5/SHA-1)",
873
+ category: "security",
874
+ severity: "high",
875
+ appliesTo: BACKEND_LANGUAGES,
876
+ frameworks: ["OWASP ASVS V6", "CWE-327", "CWE-328"],
877
+ detection: {
878
+ type: "pattern",
879
+ patterns: [
880
+ // PHP: md5($x), sha1($x)
881
+ "\\b(md5|sha1)\\s*\\(",
882
+ // .NET: MD5.Create(), SHA1.Create()
883
+ "\\b(MD5|SHA1)\\.Create\\s*\\(",
884
+ // Java: MessageDigest.getInstance("MD5"/"SHA-1")
885
+ `MessageDigest\\.getInstance\\s*\\(\\s*["'](MD5|SHA-?1)["']`
886
+ ]
887
+ },
888
+ impact: "MD5 and SHA-1 are cryptographically broken \u2014 vulnerable to collisions and brute-force. Unsuitable for password hashing or integrity checks.",
889
+ recommendation: "For passwords: bcrypt/argon2 (password_hash in PHP, BCrypt.Net in .NET, Spring Security in Java). For integrity: SHA-256, SHA-3, or BLAKE2."
890
+ },
891
+ {
892
+ id: "SEC-DESER-001",
893
+ title: "Insecure deserialization of user-controlled data",
894
+ category: "security",
895
+ severity: "high",
896
+ appliesTo: BACKEND_LANGUAGES,
897
+ frameworks: ["OWASP ASVS V5", "OWASP Top 10 A08:2021", "CWE-502"],
898
+ detection: {
899
+ type: "pattern",
900
+ patterns: [
901
+ // PHP: unserialize de superglobais
902
+ "unserialize\\s*\\(\\s*\\$_(GET|POST|REQUEST|COOKIE)",
903
+ // .NET: BinaryFormatter/SoapFormatter/NetDataContractSerializer
904
+ "\\b(BinaryFormatter|SoapFormatter|NetDataContractSerializer)\\b",
905
+ // Java: chamada readObject() — específica de ObjectInputStream/XMLDecoder
906
+ "\\.readObject\\s*\\(\\s*\\)"
907
+ ]
908
+ },
909
+ impact: "Deserializing untrusted input can lead to remote code execution via gadget chains.",
910
+ recommendation: "Avoid native serialization for untrusted input. Prefer JSON with strict schemas (json_decode, System.Text.Json, Jackson with default-typing disabled)."
911
+ },
912
+ {
913
+ id: "SEC-CMD-001",
914
+ title: "Possible command injection via user-controlled input",
915
+ category: "security",
916
+ severity: "critical",
917
+ appliesTo: BACKEND_LANGUAGES,
918
+ frameworks: ["OWASP ASVS V5", "CWE-78"],
919
+ detection: {
920
+ type: "pattern",
921
+ patterns: [
922
+ // PHP: exec/system/passthru/shell_exec/popen com superglobal nos parens
923
+ "(exec|system|passthru|shell_exec|popen|proc_open)\\s*\\([^)]*\\$_(GET|POST|REQUEST|COOKIE)",
924
+ // .NET: Process.Start com Request
925
+ "Process\\.Start\\s*\\([^)]*\\bRequest\\b",
926
+ // Java: Runtime.exec com .getParameter (qualquer variável do servlet)
927
+ "Runtime\\.getRuntime\\s*\\(\\)\\.exec\\s*\\([^)]*\\.getParameter"
928
+ ]
929
+ },
930
+ impact: "Untrusted input passed to shell execution allows attackers to run arbitrary commands on the host.",
931
+ recommendation: "Avoid shell invocation when possible. If necessary, validate input against a strict allow-list and pass arguments as an array (not concatenated string)."
932
+ },
933
+ {
934
+ id: "SEC-PATH-001",
935
+ title: "Possible path traversal via user-controlled input",
936
+ category: "security",
937
+ severity: "high",
938
+ appliesTo: BACKEND_LANGUAGES,
939
+ frameworks: ["OWASP ASVS V12", "CWE-22"],
940
+ detection: {
941
+ type: "pattern",
942
+ patterns: [
943
+ // PHP: include/require/file_get_contents/fopen/readfile com superglobal
944
+ "(file_get_contents|fopen|readfile|file)\\s*\\([^)]*\\$_(GET|POST|REQUEST|COOKIE)",
945
+ "(include|require|include_once|require_once)\\s*\\(?[^;]*\\$_(GET|POST|REQUEST|COOKIE)",
946
+ // .NET: File.ReadAllText/OpenRead/Open com Request
947
+ "File\\.(ReadAllText|ReadAllBytes|OpenRead|Open|ReadAllLines)\\s*\\([^)]*\\bRequest\\b",
948
+ // Java: new File / new FileInputStream com .getParameter (qualquer var)
949
+ "(new\\s+File|new\\s+FileInputStream|new\\s+FileReader|Files\\.read)\\s*\\([^)]*\\.getParameter"
950
+ ]
951
+ },
952
+ impact: "User-controlled file paths allow attackers to read or include arbitrary files outside the intended directory.",
953
+ recommendation: "Validate against an allow-list of filenames, normalize the path (realpath in PHP, Path.GetFullPath in .NET, Path.normalize in Java) and confirm it stays under a known root directory."
954
+ },
955
+ {
956
+ id: "SEC-XSS-001",
957
+ title: "Cross-site scripting via unescaped output",
958
+ category: "security",
959
+ severity: "high",
960
+ appliesTo: BACKEND_LANGUAGES,
961
+ frameworks: ["OWASP ASVS V5", "OWASP Top 10 A03:2021", "CWE-79"],
962
+ detection: {
963
+ type: "pattern",
964
+ patterns: [
965
+ // PHP: echo/print de superglobal sem escape
966
+ "(echo|print)[^;]*\\$_(GET|POST|REQUEST|COOKIE)",
967
+ // .NET: Response.Write com Request
968
+ "Response\\.Write\\s*\\([^)]*\\bRequest\\b"
969
+ ]
970
+ },
971
+ impact: "Reflecting user input into HTML without escaping allows script injection in victims' browsers.",
972
+ recommendation: "PHP: htmlspecialchars() / Blade {{ }} / Twig auto-escape. .NET: Razor @ syntax (auto-escapes), HttpUtility.HtmlEncode. Java: JSTL <c:out> or Thymeleaf th:text."
795
973
  }
796
974
  ];
797
975
  var CODE_QUALITY_RULES = [
@@ -825,6 +1003,27 @@ var CODE_QUALITY_RULES = [
825
1003
  },
826
1004
  impact: "Transitive dependencies may be removed in future updates, causing silent breakage.",
827
1005
  recommendation: "Declare all used packages explicitly in package.json or replace with native alternatives."
1006
+ },
1007
+ {
1008
+ id: "CQ-DEPRECATED-001",
1009
+ title: "Use of deprecated or removed language API",
1010
+ category: "legacy_code",
1011
+ severity: "medium",
1012
+ appliesTo: BACKEND_LANGUAGES,
1013
+ frameworks: ["Clean Code"],
1014
+ detection: {
1015
+ type: "pattern",
1016
+ patterns: [
1017
+ // PHP: mysql_* extension (removed in PHP 7)
1018
+ "mysql_(connect|query|fetch_array|fetch_assoc|fetch_row|num_rows|real_escape_string|select_db)\\s*\\(",
1019
+ // PHP: ereg/split (removed in PHP 7)
1020
+ "\\b(ereg|eregi|ereg_replace|split)\\s*\\(",
1021
+ // Java: legacy synchronized collections
1022
+ "\\bnew\\s+(Vector|Hashtable)\\s*[(<]"
1023
+ ]
1024
+ },
1025
+ impact: "Deprecated APIs are unsupported and may be removed in future runtime versions, causing breakage. They often have safer modern replacements.",
1026
+ recommendation: "PHP: migrate mysql_* to PDO or mysqli; ereg_* to preg_*. Java: replace Vector with ArrayList and Hashtable with HashMap (or ConcurrentHashMap if thread-safety needed)."
828
1027
  }
829
1028
  ];
830
1029
  var ALL_RULES = [...SECURITY_RULES, ...CODE_QUALITY_RULES];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "legacy-squad",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "AI-Powered Legacy Modernization Platform — Install-first, IDE-native, evidence-driven framework that transforms legacy systems into modernization-ready assets.",
5
5
  "keywords": [
6
6
  "legacy",