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.
- package/dist/cli.mjs +209 -10
- 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
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|