@greenarmor/ges-mcp-server 0.6.0 → 0.6.1

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 (3) hide show
  1. package/bundle/server.js +1200 -126
  2. package/dist/server.js +926 -127
  3. package/package.json +9 -9
package/dist/server.js CHANGED
@@ -711,13 +711,207 @@ function applyAutoFixAction(root, action) {
711
711
  }
712
712
  }
713
713
  function findMainAppFile(root) {
714
- const candidates = ["src/index.ts", "src/index.js", "src/app.ts", "src/app.js", "src/server.ts", "src/server.js", "src/main.ts", "src/main.js", "index.ts", "index.js", "app.ts", "app.js"];
715
- for (const c of candidates) {
714
+ const lang = detectProjectLanguage(root);
715
+ const candidates = {
716
+ typescript: ["src/index.ts", "src/app.ts", "src/server.ts", "src/main.ts", "index.ts", "app.ts", "server.ts"],
717
+ javascript: ["src/index.js", "src/app.js", "src/server.js", "src/main.js", "index.js", "app.js", "server.js"],
718
+ python: ["app.py", "main.py", "manage.py", "wsgi.py", "asgi.py", "src/app.py", "src/main.py"],
719
+ ruby: ["config.ru", "app.rb", "server.rb", "main.rb", "config/application.rb"],
720
+ go: ["main.go", "cmd/server/main.go", "cmd/app/main.go"],
721
+ java: ["src/main/java/com/example/Application.java", "src/main/java/Application.java"],
722
+ php: ["public/index.php", "index.php", "app.php", "app/Http/Kernel.php"],
723
+ rust: ["src/main.rs", "src/bin/main.rs", "src/app.rs"],
724
+ csharp: ["Program.cs", "Startup.cs"],
725
+ };
726
+ const exts = candidates[lang] || [];
727
+ for (const c of exts) {
716
728
  if (fs.existsSync(path.join(root, c)))
717
729
  return c;
718
730
  }
731
+ if (lang === "java") {
732
+ const found = findFileRecursive(root, "Application.java", "src/main/java");
733
+ if (found)
734
+ return found;
735
+ }
736
+ if (lang === "go") {
737
+ for (const c of ["cmd/server/main.go", "cmd/app/main.go", "main.go"]) {
738
+ if (fs.existsSync(path.join(root, c)))
739
+ return c;
740
+ }
741
+ }
719
742
  return null;
720
743
  }
744
+ function findFileRecursive(root, name, baseDir) {
745
+ const dir = path.join(root, baseDir);
746
+ if (!fs.existsSync(dir))
747
+ return null;
748
+ try {
749
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
750
+ for (const e of entries) {
751
+ if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "venv" || e.name === "__pycache__" || e.name === ".git")
752
+ continue;
753
+ const childPath = path.join(baseDir, e.name);
754
+ if (e.isDirectory()) {
755
+ const found = findFileRecursive(root, name, childPath);
756
+ if (found)
757
+ return found;
758
+ }
759
+ else if (e.name === name) {
760
+ return childPath;
761
+ }
762
+ }
763
+ }
764
+ catch { /* skip */ }
765
+ return null;
766
+ }
767
+ function detectProjectLanguage(root) {
768
+ if (fs.existsSync(path.join(root, "go.mod")))
769
+ return "go";
770
+ if (fs.existsSync(path.join(root, "Cargo.toml")))
771
+ return "rust";
772
+ if (fs.existsSync(path.join(root, "requirements.txt")) || fs.existsSync(path.join(root, "pyproject.toml")) || fs.existsSync(path.join(root, "Pipfile")) || fs.existsSync(path.join(root, "setup.py")))
773
+ return "python";
774
+ if (fs.existsSync(path.join(root, "go.mod")))
775
+ return "go";
776
+ if (fs.existsSync(path.join(root, "pom.xml")) || fs.existsSync(path.join(root, "build.gradle")) || fs.existsSync(path.join(root, "build.gradle.kts")))
777
+ return "java";
778
+ if (fs.existsSync(path.join(root, "Gemfile")))
779
+ return "ruby";
780
+ if (fs.existsSync(path.join(root, "composer.json")))
781
+ return "php";
782
+ const pkgContent = readFileSafe(path.join(root, "package.json"));
783
+ if (pkgContent) {
784
+ try {
785
+ const pkg = JSON.parse(pkgContent);
786
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
787
+ if (deps.typescript || deps["@types/node"] || fs.existsSync(path.join(root, "tsconfig.json")))
788
+ return "typescript";
789
+ return "javascript";
790
+ }
791
+ catch { /* fallthrough */ }
792
+ }
793
+ if (fs.existsSync(path.join(root, "tsconfig.json")))
794
+ return "typescript";
795
+ return "javascript";
796
+ }
797
+ function detectWebFramework(root, lang) {
798
+ if (lang === "typescript" || lang === "javascript") {
799
+ if (hasDep(root, "express"))
800
+ return "express";
801
+ if (hasDep(root, "fastify"))
802
+ return "fastify";
803
+ if (hasDep(root, "koa"))
804
+ return "koa";
805
+ if (hasDep(root, "hono"))
806
+ return "hono";
807
+ if (hasDep(root, "next"))
808
+ return "next";
809
+ if (hasDep(root, "@nestjs/core"))
810
+ return "nestjs";
811
+ if (hasDep(root, "@sveltejs/kit"))
812
+ return "sveltekit";
813
+ }
814
+ if (lang === "python") {
815
+ const reqFiles = ["requirements.txt", "pyproject.toml", "Pipfile"];
816
+ for (const f of reqFiles) {
817
+ const c = readFileSafe(path.join(root, f));
818
+ if (c) {
819
+ if (/^\s*django\b/mi.test(c) || /django/i.test(c))
820
+ return "django";
821
+ if (/^\s*flask\b/mi.test(c) || /flask/i.test(c))
822
+ return "flask";
823
+ if (/^\s*fastapi\b/mi.test(c) || /fastapi/i.test(c))
824
+ return "fastapi";
825
+ if (/^\s*sanic\b/mi.test(c) || /sanic/i.test(c))
826
+ return "sanic";
827
+ }
828
+ }
829
+ const settingsPy = readFileSafe(path.join(root, "settings.py")) || readFileSafe(path.join(root, "app/settings.py")) || readFileSafe(path.join(root, "config/settings.py"));
830
+ if (settingsPy && /DJANGO_SETTINGS_MODULE|INSTALLED_APPS|django/.test(settingsPy))
831
+ return "django";
832
+ const appPy = readFileSafe(path.join(root, "app.py")) || readFileSafe(path.join(root, "main.py"));
833
+ if (appPy) {
834
+ if (/from\s+flask\s+import|import\s+flask/.test(appPy))
835
+ return "flask";
836
+ if (/from\s+fastapi\s+import|import\s+fastapi/.test(appPy))
837
+ return "fastapi";
838
+ if (/from\s+django/.test(appPy))
839
+ return "django";
840
+ }
841
+ }
842
+ if (lang === "ruby") {
843
+ const gemfile = readFileSafe(path.join(root, "Gemfile"));
844
+ if (gemfile) {
845
+ if (/rails/i.test(gemfile))
846
+ return "rails";
847
+ if (/sinatra/i.test(gemfile))
848
+ return "sinatra";
849
+ }
850
+ }
851
+ if (lang === "go") {
852
+ const goMod = readFileSafe(path.join(root, "go.mod")) || "";
853
+ const mainGo = readFileSafe(path.join(root, "main.go")) || "";
854
+ const allGo = goMod + mainGo;
855
+ if (/gin-gonic|gin\.Default|gin\.New/.test(allGo))
856
+ return "gin";
857
+ if (/fiber\.New/.test(allGo))
858
+ return "fiber";
859
+ if (/echo\.New/.test(allGo))
860
+ return "echo";
861
+ if (/chi\.NewRouter|chi\.Mux/.test(allGo))
862
+ return "chi";
863
+ if (/mux\.NewRouter/.test(allGo))
864
+ return "gorilla";
865
+ if (/http\.ListenAndServe|http\.HandleFunc/.test(allGo))
866
+ return "nethttp";
867
+ }
868
+ if (lang === "java") {
869
+ const pom = readFileSafe(path.join(root, "pom.xml")) || "";
870
+ const gradle = readFileSafe(path.join(root, "build.gradle")) || "";
871
+ const all = pom + gradle;
872
+ if (/spring-boot|springframework/.test(all))
873
+ return "spring";
874
+ if (/ktor/.test(all))
875
+ return "ktor";
876
+ if (/quarkus/.test(all))
877
+ return "quarkus";
878
+ if (/micronaut/.test(all))
879
+ return "micronaut";
880
+ }
881
+ if (lang === "rust") {
882
+ const cargo = readFileSafe(path.join(root, "Cargo.toml")) || "";
883
+ const mainRs = readFileSafe(path.join(root, "src/main.rs")) || "";
884
+ const libRs = readFileSafe(path.join(root, "src/lib.rs")) || "";
885
+ const all = cargo + mainRs + libRs;
886
+ if (/actix-web|actix_web/.test(all))
887
+ return "actix";
888
+ if (/axum/.test(all))
889
+ return "axum";
890
+ if (/rocket/.test(all))
891
+ return "rocket";
892
+ if (/warp/.test(all))
893
+ return "warp";
894
+ }
895
+ if (lang === "php") {
896
+ const composer = readFileSafe(path.join(root, "composer.json"));
897
+ if (composer) {
898
+ try {
899
+ const pkg = JSON.parse(composer);
900
+ const req = pkg.require || {};
901
+ if (req["laravel/framework"])
902
+ return "laravel";
903
+ if (req["symfony/symfony"] || req["symfony/framework-bundle"])
904
+ return "symfony";
905
+ if (req["slim/slim"])
906
+ return "slim";
907
+ if (req["laravel/lumen-framework"])
908
+ return "lumen";
909
+ }
910
+ catch { /* skip */ }
911
+ }
912
+ }
913
+ return "generic";
914
+ }
721
915
  function hasDep(root, dep) {
722
916
  const pkg = readJsonFileSafe(path.join(root, "package.json"));
723
917
  if (!pkg)
@@ -725,6 +919,49 @@ function hasDep(root, dep) {
725
919
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
726
920
  return dep in deps;
727
921
  }
922
+ function hasPyDep(root, dep) {
923
+ for (const f of ["requirements.txt", "requirements-dev.txt"]) {
924
+ const c = readFileSafe(path.join(root, f));
925
+ if (c && new RegExp(`^\\s*${dep}\\b`, "mi").test(c))
926
+ return true;
927
+ }
928
+ const pyproject = readFileSafe(path.join(root, "pyproject.toml"));
929
+ if (pyproject && new RegExp(`^\\s*${dep}\\b`, "mi").test(pyproject))
930
+ return true;
931
+ return false;
932
+ }
933
+ function hasGoDep(root, dep) {
934
+ const goMod = readFileSafe(path.join(root, "go.mod"));
935
+ return goMod ? goMod.includes(dep) : false;
936
+ }
937
+ function hasRubyDep(root, dep) {
938
+ const gemfile = readFileSafe(path.join(root, "Gemfile"));
939
+ return gemfile ? new RegExp(`gem\\s+['"]${dep}`, "i").test(gemfile) : false;
940
+ }
941
+ function hasJavaDep(root, dep) {
942
+ const pom = readFileSafe(path.join(root, "pom.xml"));
943
+ if (pom && pom.includes(dep))
944
+ return true;
945
+ const gradle = readFileSafe(path.join(root, "build.gradle"));
946
+ return gradle ? gradle.includes(dep) : false;
947
+ }
948
+ function hasPhpDep(root, dep) {
949
+ const composer = readFileSafe(path.join(root, "composer.json"));
950
+ if (!composer)
951
+ return false;
952
+ try {
953
+ const pkg = JSON.parse(composer);
954
+ const req = { ...(pkg.require || {}), ...(pkg["require-dev"] || {}) };
955
+ return dep in req;
956
+ }
957
+ catch {
958
+ return false;
959
+ }
960
+ }
961
+ function hasRustDep(root, dep) {
962
+ const cargo = readFileSafe(path.join(root, "Cargo.toml"));
963
+ return cargo ? new RegExp(`^${dep}\\b`, "m").test(cargo) || new RegExp(`${dep}\\s*=`).test(cargo) : false;
964
+ }
728
965
  function readFileSafe(filePath) {
729
966
  try {
730
967
  return fs.readFileSync(filePath, "utf-8");
@@ -734,38 +971,208 @@ function readFileSafe(filePath) {
734
971
  }
735
972
  }
736
973
  function buildHelmetFix(root) {
737
- const appFile = findMainAppFile(root);
738
- if (!appFile)
739
- return [];
740
- const actions = [
741
- { type: "npm-install", filePath: "package.json", description: "Install helmet", ruleId: "CONFIG-001" },
742
- ];
743
- const content = readFileSafe(path.join(root, appFile));
744
- if (content && content.includes("const app = express()")) {
745
- actions.push({ type: "modify", filePath: appFile, search: "const app = express()", replace: "const app = express()\n\napp.use(helmet())", description: "Add helmet middleware", ruleId: "CONFIG-001" });
974
+ const lang = detectProjectLanguage(root);
975
+ const fw = detectWebFramework(root, lang);
976
+ const actions = [];
977
+ if (lang === "typescript" || lang === "javascript") {
978
+ const appFile = findMainAppFile(root);
979
+ if (!appFile)
980
+ return [];
981
+ if (fw === "express") {
982
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install helmet", ruleId: "CONFIG-001" });
983
+ const content = readFileSafe(path.join(root, appFile));
984
+ if (content && content.includes("const app = express()")) {
985
+ actions.push({ type: "modify", filePath: appFile, search: "const app = express()", replace: "const app = express()\n\napp.use(helmet())", description: "Add helmet middleware", ruleId: "CONFIG-001" });
986
+ }
987
+ else {
988
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'helmet';\napp.use(helmet());\n", description: "Add helmet import and middleware", ruleId: "CONFIG-001" });
989
+ }
990
+ }
991
+ else if (fw === "fastify") {
992
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install @fastify/helmet", ruleId: "CONFIG-001" });
993
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from '@fastify/helmet';\napp.register(helmet);\n", description: "Add Fastify helmet plugin", ruleId: "CONFIG-001" });
994
+ }
995
+ else if (fw === "koa") {
996
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install koa-helmet", ruleId: "CONFIG-001" });
997
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'koa-helmet';\napp.use(helmet());\n", description: "Add koa-helmet middleware", ruleId: "CONFIG-001" });
998
+ }
999
+ else if (fw === "hono") {
1000
+ actions.push({ type: "append", filePath: appFile, content: "\nimport { secureHeaders } from 'hono/secure-headers';\napp.use(secureHeaders());\n", description: "Add Hono secure headers", ruleId: "CONFIG-001" });
1001
+ }
746
1002
  }
747
- else {
748
- actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'helmet';\napp.use(helmet());\n", description: "Add helmet import and middleware", ruleId: "CONFIG-001" });
1003
+ else if (lang === "python") {
1004
+ if (fw === "django") {
1005
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Python uses django-csp/secure", ruleId: "CONFIG-001" });
1006
+ const settingsFile = findFileRecursive(root, "settings.py", ".") || "settings.py";
1007
+ actions.push({ type: "append", filePath: settingsFile, content: "\n# Security headers\nSECURE_BROWSER_XSS_FILTER = True\nSECURE_CONTENT_TYPE_NOSNIFF = True\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\nX_FRAME_OPTIONS = 'DENY'\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n", description: "Add Django security headers settings", ruleId: "CONFIG-001" });
1008
+ }
1009
+ else if (fw === "flask" || fw === "fastapi" || fw === "sanic") {
1010
+ const appFile = findMainAppFile(root) || "app.py";
1011
+ actions.push({ type: "append", filePath: appFile, content: fw === "fastapi"
1012
+ ? "\nfrom fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware\napp.add_middleware(HTTPSRedirectMiddleware)\n"
1013
+ : "\nfrom flask_talisman import Talisman\nTalisman(app, force_https=True, strict_transport_security=True, session_cookie_secure=True)\n",
1014
+ description: `Add security headers for ${fw}`, ruleId: "CONFIG-001" });
1015
+ }
1016
+ }
1017
+ else if (lang === "ruby") {
1018
+ if (fw === "rails") {
1019
+ const envFile = fs.existsSync(path.join(root, "config/environments/production.rb")) ? "config/environments/production.rb" : "config/application.rb";
1020
+ actions.push({ type: "append", filePath: envFile, content: "\nconfig.force_ssl = true\nconfig.ssl_options = { hsts: { subdomains: true, preload: true, expires: 1.year } }\nconfig.x_frame_options = 'SAMEORIGIN'\nconfig.x_content_type_options = 'nosniff'\nconfig.x_xss_protection = '1; mode=block'\nconfig.strict_transport_security = 'max-age=31536000; includeSubDomains'\n", description: "Add Rails security headers", ruleId: "CONFIG-001" });
1021
+ }
1022
+ }
1023
+ else if (lang === "go") {
1024
+ const appFile = findMainAppFile(root) || "main.go";
1025
+ if (fw === "gin" || fw === "echo" || fw === "fiber" || fw === "chi" || fw === "nethttp") {
1026
+ actions.push({ type: "append", filePath: appFile, content: "\nimport \"net/http\"\n\n// Security headers middleware\nfunc securityHeaders(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n\t\tw.Header().Set(\"X-Frame-Options\", \"DENY\")\n\t\tw.Header().Set(\"X-XSS-Protection\", \"1; mode=block\")\n\t\tw.Header().Set(\"Strict-Transport-Security\", \"max-age=31536000; includeSubDomains\")\n\t\tw.Header().Set(\"Referrer-Policy\", \"strict-origin-when-cross-origin\")\n\t\tw.Header().Set(\"Content-Security-Policy\", \"default-src 'self'\")\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n", description: "Add Go security headers middleware", ruleId: "CONFIG-001" });
1027
+ }
1028
+ }
1029
+ else if (lang === "java") {
1030
+ if (fw === "spring") {
1031
+ const hasSrc = fs.existsSync(path.join(root, "src/main/java"));
1032
+ const configPath = hasSrc ? "src/main/java/com/example/SecurityConfig.java" : "SecurityConfig.java";
1033
+ actions.push({ type: "create", filePath: configPath, content: `import org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.header.writers.StaticHeadersWriter;\n\n@Configuration\npublic class SecurityConfig {\n @Bean\n public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n http.headers()\n .contentSecurityPolicy("default-src 'self'")\n .and()\n .xssProtection()\n .and()\n .frameOptions().deny()\n .httpStrictTransportSecurity()\n .includeSubDomains(true)\n .preload(true)\n .maxAgeInSeconds(31536000);\n return http.build();\n }\n}\n`, description: "Create Spring Security config with headers", ruleId: "CONFIG-001" });
1034
+ }
1035
+ }
1036
+ else if (lang === "php") {
1037
+ if (fw === "laravel" || fw === "symfony") {
1038
+ const middleware = fw === "laravel" ? "app/Http/Middleware/SecurityHeaders.php" : "src/Middleware/SecurityHeadersMiddleware.php";
1039
+ const content = fw === "laravel"
1040
+ ? `<?php\n\nnamespace App\\Http\\Middleware;\n\nuse Closure;\n\nclass SecurityHeaders\n{\n public function handle($request, Closure $next)\n {\n $response = $next($request);\n $response->headers->set('X-Content-Type-Options', 'nosniff');\n $response->headers->set('X-Frame-Options', 'DENY');\n $response->headers->set('X-XSS-Protection', '1; mode=block');\n $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');\n $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');\n return $response;\n }\n}\n`
1041
+ : `<?php\n\nnamespace App\\Middleware;\n\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass SecurityHeadersMiddleware\n{\n public function __invoke($request, $handler)\n {\n $response = $handler->handle($request);\n $response->headers->set('X-Content-Type-Options', 'nosniff');\n $response->headers->set('X-Frame-Options', 'DENY');\n $response->headers->set('X-XSS-Protection', '1; mode=block');\n $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');\n return $response;\n }\n}\n`;
1042
+ actions.push({ type: "create", filePath: middleware, content, description: `Create security headers middleware for ${fw}`, ruleId: "CONFIG-001" });
1043
+ }
1044
+ }
1045
+ else if (lang === "rust") {
1046
+ const appFile = findMainAppFile(root) || "src/main.rs";
1047
+ if (fw === "actix") {
1048
+ actions.push({ type: "create", filePath: "src/middleware/security_headers.rs", content: `use actix_web::{HttpResponse, dev::{ServiceRequest, Service, ServiceResponse}};
1049
+
1050
+ pub fn add_security_headers(res: &mut HttpResponse) {
1051
+ res.headers_mut().insert(("X-Content-Type-Options", "nosniff"));
1052
+ res.headers_mut().insert(("X-Frame-Options", "DENY"));
1053
+ res.headers_mut().insert(("X-XSS-Protection", "1; mode=block"));
1054
+ res.headers_mut().insert(("Strict-Transport-Security", "max-age=31536000; includeSubDomains"));
1055
+ res.headers_mut().insert(("Referrer-Policy", "strict-origin-when-cross-origin"));
1056
+ res.headers_mut().insert(("Content-Security-Policy", "default-src 'self'"));
1057
+ }
1058
+ `, description: "Create Actix-web security headers middleware", ruleId: "CONFIG-001" });
1059
+ }
1060
+ else if (fw === "axum") {
1061
+ actions.push({ type: "create", filePath: "src/middleware/security_headers.rs", content: `use axum::{http::HeaderValue, response::Response};
1062
+
1063
+ pub async fn security_headers(mut res: Response) -> Response {
1064
+ let headers = res.headers_mut();
1065
+ headers.insert("X-Content-Type-Options", HeaderValue::from_static("nosniff"));
1066
+ headers.insert("X-Frame-Options", HeaderValue::from_static("DENY"));
1067
+ headers.insert("X-XSS-Protection", HeaderValue::from_static("1; mode=block"));
1068
+ headers.insert("Strict-Transport-Security", HeaderValue::from_static("max-age=31536000; includeSubDomains"));
1069
+ headers.insert("Referrer-Policy", HeaderValue::from_static("strict-origin-when-cross-origin"));
1070
+ headers.insert("Content-Security-Policy", HeaderValue::from_static("default-src 'self'"));
1071
+ res
1072
+ }
1073
+ `, description: "Create Axum security headers middleware", ruleId: "CONFIG-001" });
1074
+ }
1075
+ else {
1076
+ actions.push({ type: "append", filePath: appFile, content: "\n// GESF: Add security headers middleware\n// actix-web: use actix_web::middleware::DefaultHeaders\n// axum: use tower-http::set-header::SetResponseHeader\n// rocket: use rocket::fairing\n", description: "Add Rust security headers guidance", ruleId: "CONFIG-001" });
1077
+ }
749
1078
  }
750
1079
  return actions;
751
1080
  }
752
1081
  function buildCorsFix(root) {
753
- const appFile = findMainAppFile(root);
754
- if (!appFile)
755
- return [];
756
- return [
757
- { type: "npm-install", filePath: "package.json", description: "Install cors", ruleId: "CONFIG-002" },
758
- { type: "append", filePath: appFile, content: "\nimport cors from 'cors';\napp.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'] }));\n", description: "Add CORS with configured origins", ruleId: "CONFIG-002" },
759
- ];
1082
+ const lang = detectProjectLanguage(root);
1083
+ const fw = detectWebFramework(root, lang);
1084
+ const actions = [];
1085
+ if (lang === "typescript" || lang === "javascript") {
1086
+ const appFile = findMainAppFile(root);
1087
+ if (!appFile)
1088
+ return [];
1089
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install cors", ruleId: "CONFIG-002" });
1090
+ if (fw === "fastify") {
1091
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install @fastify/cors", ruleId: "CONFIG-002" });
1092
+ actions.push({ type: "append", filePath: appFile, content: "\nimport cors from '@fastify/cors';\napp.register(cors, { origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'] });\n", description: "Add Fastify CORS", ruleId: "CONFIG-002" });
1093
+ }
1094
+ else {
1095
+ actions.push({ type: "append", filePath: appFile, content: "\nimport cors from 'cors';\napp.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'] }));\n", description: "Add CORS with configured origins", ruleId: "CONFIG-002" });
1096
+ }
1097
+ }
1098
+ else if (lang === "python") {
1099
+ const appFile = findMainAppFile(root) || "app.py";
1100
+ if (fw === "django") {
1101
+ const settingsFile = findFileRecursive(root, "settings.py", ".") || "settings.py";
1102
+ actions.push({ type: "append", filePath: settingsFile, content: "\nCORS_ALLOWED_ORIGINS = ['https://yourdomain.com']\nCORS_ALLOW_CREDENTIALS = True\n", description: "Add Django CORS settings", ruleId: "CONFIG-002" });
1103
+ }
1104
+ else if (fw === "fastapi") {
1105
+ actions.push({ type: "append", filePath: appFile, content: "\nfrom fastapi.middleware.cors import CORSMiddleware\napp.add_middleware(CORSMiddleware, allow_origins=['http://localhost:3000'], allow_credentials=True, allow_methods=['*'], allow_headers=['*'])\n", description: "Add FastAPI CORS middleware", ruleId: "CONFIG-002" });
1106
+ }
1107
+ else if (fw === "flask") {
1108
+ actions.push({ type: "append", filePath: appFile, content: "\nfrom flask_cors import CORS\nCORS(app, origins=['http://localhost:3000'])\n", description: "Add Flask CORS", ruleId: "CONFIG-002" });
1109
+ }
1110
+ else {
1111
+ actions.push({ type: "append", filePath: appFile, content: "\n# CORS: Configure allowed origins in production\n# pip install flask-cors or fastapi[all]\n", description: "Add CORS note", ruleId: "CONFIG-002" });
1112
+ }
1113
+ }
1114
+ else if (lang === "ruby") {
1115
+ if (fw === "rails") {
1116
+ actions.push({ type: "append", filePath: "config/application.rb", content: "\nconfig.middleware.insert_before 0, Rack::Cors do\n allow do\n origins 'https://yourdomain.com'\n resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete]\n end\nend\n", description: "Add Rails CORS via Rack::Cors", ruleId: "CONFIG-002" });
1117
+ }
1118
+ }
1119
+ else if (lang === "go") {
1120
+ const appFile = findMainAppFile(root) || "main.go";
1121
+ actions.push({ type: "append", filePath: appFile, content: "\nimport \"net/http\"\n\nfunc corsMiddleware(allowedOrigins []string, next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\torigin := r.Header.Get(\"Origin\")\n\t\tfor _, o := range allowedOrigins {\n\t\t\tif origin == o {\n\t\t\t\tw.Header().Set(\"Access-Control-Allow-Origin\", origin)\n\t\t\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS\")\n\t\t\t\tw.Header().Set(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif r.Method == \"OPTIONS\" { w.WriteHeader(http.StatusNoContent); return }\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n", description: "Add Go CORS middleware", ruleId: "CONFIG-002" });
1122
+ }
1123
+ else if (lang === "java") {
1124
+ if (fw === "spring") {
1125
+ actions.push({ type: "create", filePath: "src/main/java/com/example/CorsConfig.java", content: `import org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.cors.CorsConfiguration;\nimport org.springframework.web.cors.UrlBasedCorsConfigurationSource;\nimport org.springframework.web.filter.CorsFilter;\n\n@Configuration\npublic class CorsConfig {\n @Bean\n public CorsFilter corsFilter() {\n CorsConfiguration config = new CorsConfiguration();\n config.addAllowedOrigin(\"https://yourdomain.com\");\n config.addAllowedHeader(\"*\");\n config.addAllowedMethod(\"*\");\n config.setAllowCredentials(true);\n UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n source.registerCorsConfiguration(\"/**\", config);\n return new CorsFilter(source);\n }\n}\n`, description: "Create Spring CORS configuration", ruleId: "CONFIG-002" });
1126
+ }
1127
+ }
1128
+ else if (lang === "rust") {
1129
+ const appFile = findMainAppFile(root) || "src/main.rs";
1130
+ if (fw === "actix") {
1131
+ actions.push({ type: "create", filePath: "src/middleware/cors.rs", content: `use actix_cors::Cors;
1132
+ use actix_web::http::header;
1133
+
1134
+ pub fn cors_config() -> Cors {
1135
+ Cors::default()
1136
+ .allowed_origin("http://localhost:3000")
1137
+ .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
1138
+ .allowed_headers(vec![header::CONTENT_TYPE, header::AUTHORIZATION])
1139
+ .max_age(3600)
1140
+ }
1141
+ `, description: "Create Actix-web CORS configuration", ruleId: "CONFIG-002" });
1142
+ }
1143
+ else if (fw === "axum") {
1144
+ actions.push({ type: "create", filePath: "src/middleware/cors.rs", content: `use tower_http::cors::{CorsLayer, Any};
1145
+ use http::Method;
1146
+
1147
+ pub fn cors_layer() -> CorsLayer {
1148
+ CorsLayer::new()
1149
+ .allow_origin(["http://localhost:3000".parse().unwrap()])
1150
+ .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
1151
+ .allow_headers(Any)
1152
+ }
1153
+ `, description: "Create Axum CORS layer", ruleId: "CONFIG-002" });
1154
+ }
1155
+ else {
1156
+ actions.push({ type: "append", filePath: appFile, content: "\n// GESF CORS: Configure allowed origins\n// actix-web: cargo add actix-cors\n// axum: cargo add tower-http --features cors\n// rocket: cargo add rocket_cors\n", description: "Add Rust CORS guidance", ruleId: "CONFIG-002" });
1157
+ }
1158
+ }
1159
+ return actions;
760
1160
  }
761
1161
  function buildEnvGitignoreFix(root) {
762
1162
  const gi = fs.existsSync(path.join(root, ".gitignore")) ? ".gitignore" : null;
1163
+ const envFiles = detectProjectLanguage(root) === "python" ? "\n.env\n.env.*\n!.env.example\n*.pyc\n__pycache__/\n"
1164
+ : detectProjectLanguage(root) === "go" ? "\n.env\n.env.*\n!.env.example\n*.exe\n"
1165
+ : detectProjectLanguage(root) === "ruby" ? "\n.env\n.env.*\n!.env.example\n*.gem\n"
1166
+ : detectProjectLanguage(root) === "java" ? "\n.env\n.env.*\n!.env.example\n*.class\ntarget/\n"
1167
+ : detectProjectLanguage(root) === "php" ? "\n.env\n.env.*\n!.env.example\nvendor/\n"
1168
+ : detectProjectLanguage(root) === "rust" ? "\n.env\n.env.*\n!.env.example\ntarget/\n*.key\n*.pem\n"
1169
+ : "\n.env\n.env.*\n!.env.example\n";
763
1170
  if (!gi)
764
1171
  return buildGitignoreCreateFix(root);
765
1172
  const content = readFileSafe(path.join(root, gi)) || "";
766
1173
  if (content.includes(".env"))
767
1174
  return [];
768
- return [{ type: "append", filePath: ".gitignore", content: "\n.env\n.env.*\n!.env.example\n", description: "Add .env to .gitignore", ruleId: "CONFIG-004" }];
1175
+ return [{ type: "append", filePath: ".gitignore", content: envFiles, description: "Add .env to .gitignore", ruleId: "CONFIG-004" }];
769
1176
  }
770
1177
  function buildDockerNonRootFix(root) {
771
1178
  if (!fs.existsSync(path.join(root, "Dockerfile")))
@@ -776,7 +1183,19 @@ function buildTLSFix(root, f) {
776
1183
  return [{ type: "modify", filePath: f.file, search: "NODE_TLS_REJECT_UNAUTHORIZED=0", replace: "NODE_TLS_REJECT_UNAUTHORIZED=1", description: "Re-enable TLS verification", ruleId: "CONFIG-007" }];
777
1184
  }
778
1185
  function buildGitignoreCreateFix(root) {
779
- return [{ type: "create", filePath: ".gitignore", content: "node_modules/\n.env\n.env.*\n!.env.example\ndist/\nbuild/\n*.key\n*.pem\ncoverage/\n.DS_Store\n", description: "Create .gitignore with security entries", ruleId: "CONFIG-008" }];
1186
+ const lang = detectProjectLanguage(root);
1187
+ const templates = {
1188
+ typescript: "node_modules/\n.env\n.env.*\n!.env.example\ndist/\nbuild/\n*.key\n*.pem\ncoverage/\n.DS_Store\n",
1189
+ javascript: "node_modules/\n.env\n.env.*\n!.env.example\ndist/\nbuild/\n*.key\n*.pem\ncoverage/\n.DS_Store\n",
1190
+ python: "__pycache__/\n*.pyc\n*.pyo\n.env\n.env.*\n!.env.example\n*.key\n*.pem\n.pytest_cache/\n.venv/\nvenv/\n*.egg-info/\ndist/\nbuild/\n.DS_Store\n",
1191
+ ruby: ".env\n.env.*\n!.env.example\n*.key\n*.pem\nlog/\ntmp/\n*.gem\n.DS_Store\n",
1192
+ go: ".env\n.env.*\n!.env.example\n*.key\n*.pem\n*.exe\n/bin/\n.DS_Store\n",
1193
+ java: ".env\n.env.*\n!.env.example\n*.key\n*.pem\n*.class\ntarget/\n.idea/\n*.iml\n.DS_Store\n",
1194
+ php: ".env\n.env.*\n!.env.example\nvendor/\n*.key\n*.pem\n.DS_Store\n",
1195
+ rust: "target/\nCargo.lock\n.env\n.env.*\n!.env.example\n*.key\n*.pem\n.DS_Store\n",
1196
+ csharp: ".env\n.env.*\n!.env.example\nbin/\nobj/\n*.key\n*.pem\n.DS_Store\n",
1197
+ };
1198
+ return [{ type: "create", filePath: ".gitignore", content: templates[lang] || templates.javascript, description: `Create .gitignore for ${lang} project`, ruleId: "CONFIG-008" }];
780
1199
  }
781
1200
  function buildGitignoreEntryFix(root, f) {
782
1201
  const entry = f.fix.replace("Add ", "").replace(" to .gitignore.", "");
@@ -785,15 +1204,57 @@ function buildGitignoreEntryFix(root, f) {
785
1204
  return [{ type: "append", filePath: ".gitignore", content: `\n${entry}\n`, description: `Add ${entry} to .gitignore`, ruleId: "CONFIG-009" }];
786
1205
  }
787
1206
  function buildLoggingFix(root) {
788
- const appFile = findMainAppFile(root);
789
- const hasSrc = fs.existsSync(path.join(root, "src"));
790
- const loggerPath = hasSrc ? "src/lib/logger.ts" : "lib/logger.ts";
791
- const actions = [
792
- { type: "npm-install", filePath: "package.json", description: "Install pino logger", ruleId: "CONFIG-010" },
793
- { type: "create", filePath: loggerPath, content: `import pino from 'pino';\n\nconst logger = pino({\n level: process.env.LOG_LEVEL || 'info',\n timestamp: pino.stdTimeFunctions.isoTime,\n});\n\ninterface AuditLogParams {\n userId: string;\n action: string;\n resource: string;\n ipAddress: string;\n metadata?: Record<string, unknown>;\n}\n\nexport function auditLog(params: AuditLogParams): void {\n logger.info({ ...params, timestamp: new Date().toISOString(), type: 'audit' });\n}\n\nexport default logger;\n`, description: "Create structured logger with audit logging", ruleId: "CONFIG-010" },
794
- ];
795
- if (appFile) {
796
- actions.push({ type: "append", filePath: appFile, content: `\nimport logger from './${hasSrc ? "lib/logger" : (hasSrc ? "src/lib/logger" : "lib/logger")}';\n`, description: "Import logger", ruleId: "CONFIG-010" });
1207
+ const lang = detectProjectLanguage(root);
1208
+ const actions = [];
1209
+ if (lang === "typescript" || lang === "javascript") {
1210
+ const hasSrc = fs.existsSync(path.join(root, "src"));
1211
+ const loggerPath = hasSrc ? "src/lib/logger.ts" : "lib/logger.ts";
1212
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install pino logger", ruleId: "CONFIG-010" });
1213
+ actions.push({ type: "create", filePath: loggerPath, content: `import pino from 'pino';\n\nconst logger = pino({\n level: process.env.LOG_LEVEL || 'info',\n timestamp: pino.stdTimeFunctions.isoTime,\n});\n\ninterface AuditLogParams {\n userId: string;\n action: string;\n resource: string;\n ipAddress: string;\n metadata?: Record<string, unknown>;\n}\n\nexport function auditLog(params: AuditLogParams): void {\n logger.info({ ...params, timestamp: new Date().toISOString(), type: 'audit' });\n}\n\nexport default logger;\n`, description: "Create structured logger with audit logging", ruleId: "CONFIG-010" });
1214
+ }
1215
+ else if (lang === "python") {
1216
+ actions.push({ type: "create", filePath: "lib/logger.py", content: `import logging\nimport json\nfrom datetime import datetime\n\nlogger = logging.getLogger("audit")\nlogger.setLevel(logging.INFO)\n\nhandler = logging.StreamHandler()\nhandler.setFormatter(logging.Formatter('%(message)s'))\nlogger.addHandler(handler)\n\ndef audit_log(user_id: str, action: str, resource: str, ip_address: str, **metadata):\n entry = {\n "userId": user_id,\n "action": action,\n "resource": resource,\n "ipAddress": ip_address,\n "timestamp": datetime.utcnow().isoformat() + "Z",\n "type": "audit",\n **metadata,\n }\n logger.info(json.dumps(entry))\n`, description: "Create Python audit logger", ruleId: "CONFIG-010" });
1217
+ }
1218
+ else if (lang === "ruby") {
1219
+ actions.push({ type: "create", filePath: "lib/audit_logger.rb", content: `require 'logger'\nrequire 'json'\n\nclass AuditLogger\n def initialize(logdev = $stdout)\n @logger = Logger.new(logdev)\n @logger.formatter = proc { |_, _, _, msg| msg }\n end\n\n def audit_log(user_id:, action:, resource:, ip_address:, **metadata)\n entry = {\n userId: user_id,\n action: action,\n resource: resource,\n ipAddress: ip_address,\n timestamp: Time.now.utc.iso8601,\n type: 'audit',\n **metadata,\n }\n @logger.info(entry.to_json)\n end\nend\n\nAUDIT = AuditLogger.new\n`, description: "Create Ruby audit logger", ruleId: "CONFIG-010" });
1220
+ }
1221
+ else if (lang === "go") {
1222
+ actions.push({ type: "create", filePath: "lib/audit.go", content: `package lib\n\nimport (\n\t"encoding/json"\n\t"log"\n\t"os"\n\t"time"\n)\n\ntype AuditEntry struct {\n\tUserID string "json:\\"userId\\""\n\tAction string "json:\\"action\\""\n\tResource string "json:\\"resource\\""\n\tIPAddress string "json:\\"ipAddress\\""\n\tTimestamp string "json:\\"timestamp\\""\n\tType string "json:\\"type\\""\n\tMetadata map[string]interface{} "json:\\"metadata,omitempty\\""\n}\n\nvar auditLogger = log.New(os.Stdout, "", 0)\n\nfunc AuditLog(userID, action, resource, ipAddr string, metadata map[string]interface{}) {\n\tentry := AuditEntry{\n\t\tUserID: userID,\n\t\tAction: action,\n\t\tResource: resource,\n\t\tIPAddress: ipAddr,\n\t\tTimestamp: time.Now().UTC().Format(time.RFC3339),\n\t\tType: "audit",\n\t\tMetadata: metadata,\n\t}\n\tdata, _ := json.Marshal(entry)\n\tauditLogger.Println(string(data))\n}\n`, description: "Create Go audit logger", ruleId: "CONFIG-010" });
1223
+ }
1224
+ else if (lang === "java") {
1225
+ actions.push({ type: "create", filePath: "src/main/java/com/example/AuditLogger.java", content: `package com.example;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport java.time.Instant;\nimport java.util.Map;\n\npublic class AuditLogger {\n private static final Logger logger = LoggerFactory.getLogger("audit");\n private static final ObjectMapper mapper = new ObjectMapper();\n\n public static void auditLog(String userId, String action, String resource, String ipAddress, Map<String, Object> metadata) {\n try {\n Map<String, Object> entry = Map.of(\n "userId", userId,\n "action", action,\n "resource", resource,\n "ipAddress", ipAddress,\n "timestamp", Instant.now().toString(),\n "type", "audit"\n );\n if (metadata != null) entry.putAll(metadata);\n logger.info(mapper.writeValueAsString(entry));\n } catch (Exception e) {\n logger.error("Audit log failed", e);\n }\n }\n}\n`, description: "Create Java audit logger", ruleId: "CONFIG-010" });
1226
+ }
1227
+ else if (lang === "php") {
1228
+ actions.push({ type: "create", filePath: "lib/audit_logger.php", content: `<?php\n\nclass AuditLogger\n{\n public static function log(string $userId, string $action, string $resource, string $ipAddress, array $metadata = []): void\n {\n $entry = array_merge([\n 'userId' => $userId,\n 'action' => $action,\n 'resource' => $resource,\n 'ipAddress' => $ipAddress,\n 'timestamp' => gmdate('c'),\n 'type' => 'audit',\n ], $metadata);\n error_log(json_encode($entry));\n }\n}\n`, description: "Create PHP audit logger", ruleId: "CONFIG-010" });
1229
+ }
1230
+ else if (lang === "rust") {
1231
+ actions.push({ type: "create", filePath: "src/logger.rs", content: `use serde_json::json;
1232
+ use tracing::{info, instrument};
1233
+ use chrono::Utc;
1234
+
1235
+ #[derive(Debug, serde::Serialize)]
1236
+ pub struct AuditEntry {
1237
+ pub user_id: String,
1238
+ pub action: String,
1239
+ pub resource: String,
1240
+ pub ip_address: String,
1241
+ pub timestamp: String,
1242
+ #[serde(rename = "type")]
1243
+ pub entry_type: String,
1244
+ }
1245
+
1246
+ pub fn audit_log(user_id: &str, action: &str, resource: &str, ip_address: &str) {
1247
+ let entry = AuditEntry {
1248
+ user_id: user_id.to_string(),
1249
+ action: action.to_string(),
1250
+ resource: resource.to_string(),
1251
+ ip_address: ip_address.to_string(),
1252
+ timestamp: Utc::now().to_rfc3339(),
1253
+ entry_type: "audit".to_string(),
1254
+ };
1255
+ info!("{}", serde_json::to_string(&entry).unwrap_or_default());
1256
+ }
1257
+ `, description: "Create Rust audit logger (tracing)", ruleId: "CONFIG-010" });
797
1258
  }
798
1259
  return actions;
799
1260
  }
@@ -807,18 +1268,41 @@ function buildSecretsFix(root, f) {
807
1268
  if (idx >= lines.length)
808
1269
  return actions;
809
1270
  const line = lines[idx];
1271
+ const lang = detectProjectLanguage(root);
810
1272
  const match = line.match(/(\w+)\s*[:=]\s*['"]([^'"]+)['"]/);
811
1273
  if (match) {
812
1274
  const varName = match[1];
813
1275
  const value = match[2];
814
- const envFile = fs.existsSync(path.join(root, ".env")) ? ".env" : ".env";
815
- actions.push({ type: "append", filePath: envFile, content: `\n${varName}=${value}\n`, description: `Move ${varName} to .env`, ruleId: "SECRETS-001" });
816
- actions.push({ type: "modify", filePath: f.file, search: line, replace: `${varName}: process.env.${varName}`, description: `Replace hardcoded ${varName}`, ruleId: "SECRETS-001" });
1276
+ actions.push({ type: "append", filePath: ".env", content: `\n${varName}=${value}\n`, description: `Move ${varName} to .env`, ruleId: "SECRETS-001" });
1277
+ let replacement;
1278
+ if (lang === "python") {
1279
+ replacement = line.replace(match[0], `${varName} = os.environ.get('${varName}')`);
1280
+ }
1281
+ else if (lang === "ruby") {
1282
+ replacement = line.replace(match[0], `${varName} = ENV['${varName}']`);
1283
+ }
1284
+ else if (lang === "go") {
1285
+ replacement = line.replace(match[0], `${varName} := os.Getenv("${varName}")`);
1286
+ }
1287
+ else if (lang === "java") {
1288
+ replacement = line.replace(match[0], `String ${varName} = System.getenv("${varName}")`);
1289
+ }
1290
+ else if (lang === "php") {
1291
+ replacement = line.replace(match[0], `$${varName} = getenv('${varName}')`);
1292
+ }
1293
+ else if (lang === "rust") {
1294
+ replacement = line.replace(match[0], `let ${varName} = std::env::var("${varName}").unwrap_or_default()`);
1295
+ }
1296
+ else {
1297
+ replacement = `${varName}: process.env.${varName}`;
1298
+ }
1299
+ actions.push({ type: "modify", filePath: f.file, search: line, replace: replacement, description: `Replace hardcoded ${varName} with env variable`, ruleId: "SECRETS-001" });
817
1300
  actions.push(...buildEnvGitignoreFix(root));
818
1301
  }
819
1302
  return actions;
820
1303
  }
821
1304
  function buildWeakHashFix(root, f) {
1305
+ const lang = detectProjectLanguage(root);
822
1306
  const content = readFileSafe(path.join(root, f.file));
823
1307
  if (!content)
824
1308
  return [];
@@ -828,130 +1312,356 @@ function buildWeakHashFix(root, f) {
828
1312
  return [];
829
1313
  const line = lines[idx];
830
1314
  let replacement = line;
831
- if (/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/.test(line)) {
832
- replacement = line.replace(/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/, "createHash('sha256')");
1315
+ if (lang === "python") {
1316
+ replacement = line.replace(/hashlib\.md5\(/gi, "hashlib.sha256(").replace(/hashlib\.sha1\(/gi, "hashlib.sha256(");
1317
+ }
1318
+ else if (lang === "go") {
1319
+ replacement = line.replace(/md5\.New\(\)/gi, "sha256.New()").replace(/sha1\.New\(\)/gi, "sha256.New()");
833
1320
  }
834
- else if (/hashlib\.md5\(/i.test(line)) {
835
- replacement = line.replace(/hashlib\.md5\(/gi, "hashlib.sha256(");
1321
+ else if (lang === "ruby") {
1322
+ replacement = line.replace(/Digest::MD5/gi, "Digest::SHA256").replace(/Digest::SHA1/gi, "Digest::SHA256");
836
1323
  }
837
- else if (/hashlib\.sha1\(/i.test(line)) {
838
- replacement = line.replace(/hashlib\.sha1\(/gi, "hashlib.sha256(");
1324
+ else if (lang === "java") {
1325
+ replacement = line.replace(/MessageDigest\.getInstance\(["']MD5["']\)/gi, 'MessageDigest.getInstance("SHA-256")').replace(/MessageDigest\.getInstance\(["']SHA-1["']\)/gi, 'MessageDigest.getInstance("SHA-256")');
1326
+ }
1327
+ else if (lang === "php") {
1328
+ replacement = line.replace(/md5\(/gi, "hash('sha256', ").replace(/sha1\(/gi, "hash('sha256', ");
1329
+ }
1330
+ else if (lang === "rust") {
1331
+ replacement = line.replace(/md5::compute/gi, "sha2::Sha256::digest").replace(/use md5/gi, "use sha2::{Sha256, Digest}");
1332
+ }
1333
+ else {
1334
+ replacement = line.replace(/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/, "createHash('sha256')");
839
1335
  }
840
1336
  if (replacement === line)
841
1337
  return [];
842
1338
  return [{ type: "modify", filePath: f.file, search: line, replace: replacement, description: "Replace weak hash with SHA-256", ruleId: "CRYPTO-001" }];
843
1339
  }
844
1340
  function buildPasswordFix(root, _f) {
845
- const hasSrc = fs.existsSync(path.join(root, "src"));
846
- const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
847
- const actions = [
848
- { type: "npm-install", filePath: "package.json", description: "Install argon2", ruleId: "CRYPTO-003" },
849
- ];
850
- if (!fs.existsSync(path.join(root, authPath))) {
851
- actions.push({ type: "create", filePath: authPath, content: `import argon2 from 'argon2';\n\nexport async function hashPassword(password: string): Promise<string> {\n return argon2.hash(password, { type: argon2.argon2id });\n}\n\nexport async function verifyPassword(hashedPassword: string, inputPassword: string): Promise<boolean> {\n return argon2.verify(hashedPassword, inputPassword);\n}\n`, description: "Create Argon2id password utility", ruleId: "CRYPTO-003" });
1341
+ const lang = detectProjectLanguage(root);
1342
+ const actions = [];
1343
+ if (lang === "typescript" || lang === "javascript") {
1344
+ const hasSrc = fs.existsSync(path.join(root, "src"));
1345
+ const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
1346
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install argon2", ruleId: "CRYPTO-003" });
1347
+ if (!fs.existsSync(path.join(root, authPath))) {
1348
+ actions.push({ type: "create", filePath: authPath, content: `import argon2 from 'argon2';\n\nexport async function hashPassword(password: string): Promise<string> {\n return argon2.hash(password, { type: argon2.argon2id });\n}\n\nexport async function verifyPassword(hashedPassword: string, inputPassword: string): Promise<boolean> {\n return argon2.verify(hashedPassword, inputPassword);\n}\n`, description: "Create Argon2id password utility", ruleId: "CRYPTO-003" });
1349
+ }
1350
+ }
1351
+ else if (lang === "python") {
1352
+ actions.push({ type: "create", filePath: "lib/auth.py", content: `import hashlib\nimport os\n\ndef hash_password(password: str) -> str:\n salt = os.urandom(16)\n key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)\n return salt.hex() + ':' + key.hex()\n\ndef verify_password(stored: str, provided: str) -> bool:\n salt_hex, key_hex = stored.split(':')\n salt = bytes.fromhex(salt_hex)\n new_key = hashlib.pbkdf2_hmac('sha256', provided.encode(), salt, 100000)\n return new_key.hex() == key_hex\n`, description: "Create Python password utility (PBKDF2-SHA256)", ruleId: "CRYPTO-003" });
1353
+ }
1354
+ else if (lang === "go") {
1355
+ actions.push({ type: "create", filePath: "lib/auth.go", content: `package lib\n\nimport (\n\t"crypto/rand"\n\t"crypto/subtle"\n\t"encoding/hex"\n\t"golang.org/x/crypto/argon2"\n)\n\nfunc HashPassword(password string) (string, error) {\n\tsalt := make([]byte, 16)\n\tif _, err := rand.Read(salt); err != nil {\n\t\treturn "", err\n\t}\n\thash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)\n\treturn hex.EncodeToString(salt) + ":" + hex.EncodeToString(hash), nil\n}\n\nfunc VerifyPassword(stored, provided string) (bool, error) {\n\tparts := strings.SplitN(stored, ":", 2)\n\tif len(parts) != 2 { return false, nil }\n\tsalt, _ := hex.DecodeString(parts[0])\n\tstoredHash, _ := hex.DecodeString(parts[1])\n\tprovidedHash := argon2.IDKey([]byte(provided), salt, 1, 64*1024, 4, 32)\n\treturn subtle.ConstantTimeCompare(storedHash, providedHash) == 1, nil\n}\n`, description: "Create Go Argon2id password utility", ruleId: "CRYPTO-003" });
1356
+ }
1357
+ else if (lang === "ruby") {
1358
+ actions.push({ type: "create", filePath: "lib/auth.rb", content: `require 'bcrypt'\n\ndef hash_password(password)\n BCrypt::Password.create(password)\nend\n\ndef verify_password(stored_hash, provided_password)\n BCrypt::Password.new(stored_hash) == provided_password\nend\n`, description: "Create Ruby BCrypt password utility", ruleId: "CRYPTO-003" });
1359
+ }
1360
+ else if (lang === "java") {
1361
+ actions.push({ type: "create", filePath: "src/main/java/com/example/PasswordUtil.java", content: `package com.example;\n\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.PBEKeySpec;\nimport java.security.SecureRandom;\nimport java.util.Base64;\n\npublic class PasswordUtil {\n private static final int ITERATIONS = 100000;\n private static final int KEY_LENGTH = 256;\n private static final SecureRandom RANDOM = new SecureRandom();\n\n public static String hashPassword(String password) throws Exception {\n byte[] salt = new byte[16];\n RANDOM.nextBytes(salt);\n PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);\n byte[] hash = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded();\n return Base64.getEncoder().encodeToString(salt) + ":" + Base64.getEncoder().encodeToString(hash);\n }\n\n public static boolean verifyPassword(String stored, String provided) throws Exception {\n String[] parts = stored.split(":");\n byte[] salt = Base64.getDecoder().decode(parts[0]);\n byte[] storedHash = Base64.getDecoder().decode(parts[1]);\n PBEKeySpec spec = new PBEKeySpec(provided.toCharArray(), salt, ITERATIONS, KEY_LENGTH);\n byte[] testHash = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded();\n return java.util.Arrays.equals(storedHash, testHash);\n }\n}\n`, description: "Create Java PBKDF2 password utility", ruleId: "CRYPTO-003" });
1362
+ }
1363
+ else if (lang === "php") {
1364
+ actions.push({ type: "create", filePath: "lib/auth.php", content: `<?php\n\nfunction hash_password(string $password): string {\n return password_hash($password, PASSWORD_ARGON2ID);\n}\n\nfunction verify_password(string $hash, string $password): bool {\n return password_verify($password, $hash);\n}\n`, description: "Create PHP Argon2id password utility", ruleId: "CRYPTO-003" });
1365
+ }
1366
+ else if (lang === "rust") {
1367
+ actions.push({ type: "create", filePath: "src/auth.rs", content: `use argon2::{Argon2, Algorithm, Version, Params};
1368
+ use argon2::password_hash::{SaltString, PasswordHasher, PasswordVerifier};
1369
+ use rand::rngs::OsRng;
1370
+
1371
+ pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
1372
+ let salt = SaltString::generate(&mut OsRng);
1373
+ let params = Params::new(65536, 3, 4, Some(32))?;
1374
+ let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1375
+ let hash = argon2.hash_password(password.as_bytes(), &salt)?;
1376
+ Ok(hash.to_string())
1377
+ }
1378
+
1379
+ pub fn verify_password(hash: &str, password: &str) -> Result<bool, argon2::password_hash::Error> {
1380
+ let parsed = argon2::PasswordHash::new(hash)?;
1381
+ Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
1382
+ }
1383
+ `, description: "Create Rust Argon2id password utility", ruleId: "CRYPTO-003" });
852
1384
  }
853
1385
  return actions;
854
1386
  }
855
1387
  function buildRateLimitFix(root) {
856
- const appFile = findMainAppFile(root);
857
- if (!appFile)
858
- return [];
859
- const isExpress = hasDep(root, "express");
860
- const isFastify = hasDep(root, "fastify");
861
- if (isExpress) {
862
- return [
863
- { type: "npm-install", filePath: "package.json", description: "Install express-rate-limit", ruleId: "AUTH-002" },
864
- { type: "append", filePath: appFile, content: `\nimport rateLimit from 'express-rate-limit';\n\nconst limiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 100,\n standardHeaders: true,\n legacyHeaders: false,\n});\napp.use(limiter);\n`, description: "Add rate limiting (100 req/15min)", ruleId: "AUTH-002" },
865
- ];
1388
+ const lang = detectProjectLanguage(root);
1389
+ const fw = detectWebFramework(root, lang);
1390
+ const actions = [];
1391
+ if (lang === "typescript" || lang === "javascript") {
1392
+ const appFile = findMainAppFile(root);
1393
+ if (!appFile)
1394
+ return [];
1395
+ if (fw === "express") {
1396
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install express-rate-limit", ruleId: "AUTH-002" });
1397
+ actions.push({ type: "append", filePath: appFile, content: `\nimport rateLimit from 'express-rate-limit';\n\nconst limiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 100,\n standardHeaders: true,\n legacyHeaders: false,\n});\napp.use(limiter);\n`, description: "Add rate limiting (100 req/15min)", ruleId: "AUTH-002" });
1398
+ }
1399
+ else if (fw === "fastify") {
1400
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install @fastify/rate-limit", ruleId: "AUTH-002" });
1401
+ actions.push({ type: "append", filePath: appFile, content: `\nimport rateLimit from '@fastify/rate-limit';\napp.register(rateLimit, { max: 100, timeWindow: '15 minutes' });\n`, description: "Add rate limiting to Fastify", ruleId: "AUTH-002" });
1402
+ }
866
1403
  }
867
- else if (isFastify) {
868
- return [
869
- { type: "npm-install", filePath: "package.json", description: "Install @fastify/rate-limit", ruleId: "AUTH-002" },
870
- { type: "append", filePath: appFile, content: `\nimport rateLimit from '@fastify/rate-limit';\napp.register(rateLimit, { max: 100, timeWindow: '15 minutes' });\n`, description: "Add rate limiting to Fastify", ruleId: "AUTH-002" },
871
- ];
1404
+ else if (lang === "python") {
1405
+ const appFile = findMainAppFile(root) || "app.py";
1406
+ if (fw === "django") {
1407
+ actions.push({ type: "append", filePath: appFile, content: "\n# Rate limiting: pip install django-ratelimit\n# Add to views: @ratelimit(key='ip', rate='100/h', block=True)\n", description: "Add Django rate limiting note", ruleId: "AUTH-002" });
1408
+ }
1409
+ else if (fw === "fastapi") {
1410
+ actions.push({ type: "append", filePath: appFile, content: "\nfrom slowapi import Limiter\nfrom slowapi.util import get_remote_address\n\nlimiter = Limiter(key_func=get_remote_address)\n# Add to routes: @limiter.limit('100/15minutes')\n", description: "Add FastAPI rate limiting (slowapi)", ruleId: "AUTH-002" });
1411
+ }
1412
+ else if (fw === "flask") {
1413
+ actions.push({ type: "append", filePath: appFile, content: "\nfrom flask_limiter import Limiter\nfrom flask_limiter.util import get_remote_address\n\nlimiter = Limiter(app=app, key_func=get_remote_address, default_limits=['100 per 15 minute'])\n", description: "Add Flask rate limiting", ruleId: "AUTH-002" });
1414
+ }
872
1415
  }
873
- return [];
1416
+ else if (lang === "ruby") {
1417
+ if (fw === "rails") {
1418
+ actions.push({ type: "append", filePath: "Gemfile", content: "\ngem 'rack-attack'\n", description: "Add rack-attack for rate limiting", ruleId: "AUTH-002" });
1419
+ actions.push({ type: "append", filePath: "config/application.rb", content: "\nconfig.middleware.use Rack::Attack\nRack::Attack.throttle('req/ip', limit: 100, period: 15.minutes) { |req| req.ip }\n", description: "Add Rails rate limiting config", ruleId: "AUTH-002" });
1420
+ }
1421
+ }
1422
+ else if (lang === "go") {
1423
+ const appFile = findMainAppFile(root) || "main.go";
1424
+ actions.push({ type: "append", filePath: appFile, content: "\nimport (\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype rateLimiter struct {\n\tmu sync.Mutex\n\tvisitors map[string][]time.Time\n\tlimit int\n\twindow time.Duration\n}\n\nfunc newRateLimiter(limit int, window time.Duration) *rateLimiter {\n\treturn &rateLimiter{visitors: make(map[string][]time.Time), limit: limit, window: window}\n}\n\nfunc (rl *rateLimiter) allow(ip string) bool {\n\trl.mu.Lock()\n\tdefer rl.mu.Unlock()\n\tnow := time.Now()\n\twindowStart := now.Add(-rl.window)\n\tvar recent []time.Time\n\tfor _, t := range rl.visitors[ip] {\n\t\tif t.After(windowStart) { recent = append(recent, t) }\n\t}\n\trl.visitors[ip] = recent\n\tif len(recent) >= rl.limit { return false }\n\trl.visitors[ip] = append(rl.visitors[ip], now)\n\treturn true\n}\n\nvar limiter = newRateLimiter(100, 15*time.Minute)\n\nfunc rateLimitMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif !limiter.allow(r.RemoteAddr) {\n\t\t\thttp.Error(w, \"Too many requests\", http.StatusTooManyRequests)\n\t\t\treturn\n\t\t}\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n", description: "Add Go rate limiter middleware", ruleId: "AUTH-002" });
1425
+ }
1426
+ else if (lang === "java") {
1427
+ if (fw === "spring") {
1428
+ actions.push({ type: "create", filePath: "src/main/java/com/example/RateLimitConfig.java", content: `package com.example;\n\nimport io.github.bucket4j.Bandwidth;\nimport io.github.bucket4j.Bucket;\nimport io.github.bucket4j.Refill;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.time.Duration;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n@Component\npublic class RateLimitInterceptor implements HandlerInterceptor {\n private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();\n\n private Bucket newBucket() {\n Bandwidth limit = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(15)));\n return Bucket.builder().addLimit(limit).build();\n }\n\n @Override\n public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {\n Bucket bucket = buckets.computeIfAbsent(request.getRemoteAddr(), k -> newBucket());\n if (bucket.tryConsume(1)) return true;\n response.setStatus(429);\n return false;\n }\n}\n`, description: "Create Spring rate limiter (bucket4j)", ruleId: "AUTH-002" });
1429
+ }
1430
+ }
1431
+ else if (lang === "php") {
1432
+ const appFile = findMainAppFile(root) || "public/index.php";
1433
+ actions.push({ type: "append", filePath: appFile, content: "\n// Rate limiting middleware\n$ip = $_SERVER['REMOTE_ADDR'];\n$limit = 100;\n$window = 900; // 15 minutes\n$cacheKey = 'rate_limit_' . $ip;\n// Implement with your cache layer (Redis, APCu, file-based)\n", description: "Add PHP rate limiting scaffolding", ruleId: "AUTH-002" });
1434
+ }
1435
+ else if (lang === "rust") {
1436
+ const appFile = findMainAppFile(root) || "src/main.rs";
1437
+ if (fw === "actix") {
1438
+ actions.push({ type: "append", filePath: appFile, content: "\n// Rate limiting: cargo add actix-governor\n// use actix_governor::{GovernorConfigBuilder, Governor};\n// let governor_conf = GovernorConfigBuilder::default()\n// .per_second(1)\n// .burst_size(20)\n// .finish()\n// .unwrap();\n// app.wrap(Governor::new(&governor_conf));\n", description: "Add Actix-web rate limiting (actix-governor)", ruleId: "AUTH-002" });
1439
+ }
1440
+ else if (fw === "axum") {
1441
+ actions.push({ type: "append", filePath: appFile, content: "\n// Rate limiting: cargo add tower --features limit\n// use tower::ServiceBuilder;\n// use tower::limit::RateLimitLayer;\n// use std::time::Duration;\n// let app = axum::Router::new()\n// .layer(ServiceBuilder::new()\n// .layer(RateLimitLayer::new(100, Duration::from_secs(900))));\n", description: "Add Axum rate limiting (tower)", ruleId: "AUTH-002" });
1442
+ }
1443
+ else {
1444
+ actions.push({ type: "append", filePath: appFile, content: "\n// GESF Rate Limiting: 100 requests per 15 minutes\n// actix-web: cargo add actix-governor\n// axum: cargo add tower --features limit\n", description: "Add Rust rate limiting guidance", ruleId: "AUTH-002" });
1445
+ }
1446
+ }
1447
+ return actions;
874
1448
  }
875
1449
  function buildSessionTimeoutFix(root) {
876
- const appFile = findMainAppFile(root);
877
- if (!appFile)
878
- return [];
879
- const isExpress = hasDep(root, "express");
880
- if (!isExpress)
881
- return [{ type: "append", filePath: appFile, content: "\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n", description: "Add session timeout constant", ruleId: "AUTH-003" }];
882
- const content = readFileSafe(path.join(root, appFile)) || "";
883
- if (content.includes("session("))
884
- return [];
885
- return [
886
- { type: "npm-install", filePath: "package.json", description: "Install express-session", ruleId: "AUTH-003" },
887
- { type: "append", filePath: appFile, content: `\nimport session from 'express-session';\n\napp.use(session({\n secret: process.env.SESSION_SECRET || 'change-me-in-production',\n resave: false,\n saveUninitialized: false,\n cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 30 * 60 * 1000 },\n}));\n`, description: "Add session with 30-min timeout", ruleId: "AUTH-003" },
888
- ];
1450
+ const lang = detectProjectLanguage(root);
1451
+ const fw = detectWebFramework(root, lang);
1452
+ const actions = [];
1453
+ if (lang === "typescript" || lang === "javascript") {
1454
+ const appFile = findMainAppFile(root);
1455
+ if (!appFile)
1456
+ return [];
1457
+ if (fw === "express") {
1458
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install express-session", ruleId: "AUTH-003" });
1459
+ actions.push({ type: "append", filePath: appFile, content: `\nimport session from 'express-session';\n\napp.use(session({\n secret: process.env.SESSION_SECRET || 'change-me-in-production',\n resave: false,\n saveUninitialized: false,\n cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 30 * 60 * 1000 },\n}));\n`, description: "Add session with 30-min timeout", ruleId: "AUTH-003" });
1460
+ }
1461
+ else {
1462
+ actions.push({ type: "append", filePath: appFile, content: "\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n", description: "Add session timeout constant", ruleId: "AUTH-003" });
1463
+ }
1464
+ }
1465
+ else if (lang === "python") {
1466
+ if (fw === "django") {
1467
+ const settingsFile = findFileRecursive(root, "settings.py", ".") || "settings.py";
1468
+ actions.push({ type: "append", filePath: settingsFile, content: "\nSESSION_COOKIE_AGE = 1800 # 30 minutes\nSESSION_COOKIE_SECURE = True\nSESSION_COOKIE_HTTPONLY = True\nSESSION_EXPIRE_AT_BROWSER_CLOSE = True\n", description: "Add Django session timeout settings", ruleId: "AUTH-003" });
1469
+ }
1470
+ else {
1471
+ const appFile = findMainAppFile(root) || "app.py";
1472
+ actions.push({ type: "append", filePath: appFile, content: "\n# Session timeout: 30 minutes\nSESSION_TIMEOUT = 30 * 60\n", description: "Add session timeout constant", ruleId: "AUTH-003" });
1473
+ }
1474
+ }
1475
+ else if (lang === "ruby") {
1476
+ if (fw === "rails") {
1477
+ actions.push({ type: "append", filePath: "config/initializers/session_store.rb", content: "\nRails.application.config.session_store :cookie_store, expire_after: 30.minutes, secure: Rails.env.production?, httponly: true\n", description: "Add Rails session timeout", ruleId: "AUTH-003" });
1478
+ }
1479
+ }
1480
+ else if (lang === "go") {
1481
+ const appFile = findMainAppFile(root) || "main.go";
1482
+ actions.push({ type: "append", filePath: appFile, content: "\nconst sessionTimeout = 30 * time.Minute\n", description: "Add Go session timeout constant", ruleId: "AUTH-003" });
1483
+ }
1484
+ else if (lang === "java") {
1485
+ if (fw === "spring") {
1486
+ actions.push({ type: "append", filePath: "src/main/resources/application.properties", content: "\nserver.servlet.session.timeout=30m\nserver.servlet.session.cookie.http-only=true\nserver.servlet.session.cookie.secure=true\n", description: "Add Spring session timeout config", ruleId: "AUTH-003" });
1487
+ }
1488
+ }
1489
+ else if (lang === "php") {
1490
+ if (fw === "laravel") {
1491
+ actions.push({ type: "append", filePath: "config/session.php", content: "\n'lifetime' => 30,\n'expire_on_close' => true,\n'secure' => env('APP_ENV') === 'production',\n'http_only' => true,\n", description: "Add Laravel session timeout", ruleId: "AUTH-003" });
1492
+ }
1493
+ else {
1494
+ const appFile = findMainAppFile(root) || "public/index.php";
1495
+ actions.push({ type: "append", filePath: appFile, content: "\nini_set('session.gc_maxlifetime', 1800); // 30 minutes\nsession_set_cookie_params(1800, '/', '', true, true);\n", description: "Add PHP session timeout config", ruleId: "AUTH-003" });
1496
+ }
1497
+ }
1498
+ else if (lang === "rust") {
1499
+ const appFile = findMainAppFile(root) || "src/main.rs";
1500
+ actions.push({ type: "append", filePath: appFile, content: "\nconst SESSION_TIMEOUT_SECS: u64 = 30 * 60; // 30 minutes\n", description: "Add Rust session timeout constant", ruleId: "AUTH-003" });
1501
+ }
1502
+ return actions;
889
1503
  }
890
1504
  function buildCORSWildcardFix(root) {
1505
+ const lang = detectProjectLanguage(root);
891
1506
  const appFile = findMainAppFile(root);
892
1507
  if (!appFile)
893
1508
  return [];
894
1509
  const content = readFileSafe(path.join(root, appFile)) || "";
895
1510
  const actions = [];
896
- if (content.includes("origin: '*'")) {
897
- actions.push({ type: "modify", filePath: appFile, search: "origin: '*'", replace: "origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']", description: "Replace CORS wildcard", ruleId: "AUTH-004" });
898
- }
899
- if (content.includes('origin:"*"')) {
900
- actions.push({ type: "modify", filePath: appFile, search: 'origin:"*"', replace: "origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']", description: "Replace CORS wildcard", ruleId: "AUTH-004" });
1511
+ const wildcardPatterns = ["origin: '*'", "origin:'*'", 'origin:"*"', "Access-Control-Allow-Origin: *"];
1512
+ for (const pattern of wildcardPatterns) {
1513
+ if (!content.includes(pattern))
1514
+ continue;
1515
+ if (lang === "python") {
1516
+ const replacement = pattern.includes("*'") || pattern.includes('*"')
1517
+ ? "origins=['http://localhost:3000']"
1518
+ : "origins=['http://localhost:3000']";
1519
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: replacement, description: "Replace CORS wildcard", ruleId: "AUTH-004" });
1520
+ }
1521
+ else if (lang === "go") {
1522
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: 'w.Header().Set("Access-Control-Allow-Origin", os.Getenv("ALLOWED_ORIGIN"))', description: "Replace CORS wildcard with env var", ruleId: "AUTH-004" });
1523
+ }
1524
+ else if (lang === "ruby") {
1525
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: "origins ENV.fetch('ALLOWED_ORIGINS', 'http://localhost:3000').split(',')", description: "Replace CORS wildcard with env var", ruleId: "AUTH-004" });
1526
+ }
1527
+ else if (lang === "java") {
1528
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: 'config.addAllowedOrigin(System.getenv("ALLOWED_ORIGIN"))', description: "Replace CORS wildcard with env var", ruleId: "AUTH-004" });
1529
+ }
1530
+ else if (lang === "php") {
1531
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: "$response->headers->set('Access-Control-Allow-Origin', getenv('ALLOWED_ORIGIN'))", description: "Replace CORS wildcard with env var", ruleId: "AUTH-004" });
1532
+ }
1533
+ else if (lang === "rust") {
1534
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: "allowed_origin(std::env::var(\"ALLOWED_ORIGIN\").unwrap_or(\"http://localhost:3000\".to_string()))", description: "Replace CORS wildcard with env var", ruleId: "AUTH-004" });
1535
+ }
1536
+ else {
1537
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: "origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']", description: "Replace CORS wildcard", ruleId: "AUTH-004" });
1538
+ }
901
1539
  }
902
1540
  return actions;
903
1541
  }
904
1542
  function buildTimestampsFix(root, f) {
905
- if (!f.file.endsWith(".prisma"))
906
- return [];
907
- const content = readFileSafe(path.join(root, f.file));
908
- if (!content)
909
- return [];
910
- const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
911
- if (!modelMatch || modelMatch.length === 0)
912
- return [];
913
- const block = modelMatch[0];
914
- const closingBrace = block.lastIndexOf("}");
915
- if (closingBrace === -1)
916
- return [];
917
- const insertion = "\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt";
918
- return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + insertion + block.slice(closingBrace), description: "Add createdAt/updatedAt to Prisma model", ruleId: "DB-001" }];
1543
+ if (f.file.endsWith(".prisma")) {
1544
+ const content = readFileSafe(path.join(root, f.file));
1545
+ if (!content)
1546
+ return [];
1547
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
1548
+ if (!modelMatch || modelMatch.length === 0)
1549
+ return [];
1550
+ const block = modelMatch[0];
1551
+ const closingBrace = block.lastIndexOf("}");
1552
+ if (closingBrace === -1)
1553
+ return [];
1554
+ const insertion = "\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt";
1555
+ return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + insertion + block.slice(closingBrace), description: "Add createdAt/updatedAt to Prisma model", ruleId: "DB-001" }];
1556
+ }
1557
+ if (f.file.endsWith(".py")) {
1558
+ return [{ type: "append", filePath: f.file, content: "\n# GESF: Add audit timestamps\n# For Django models:\n# created_at = models.DateTimeField(auto_now_add=True)\n# updated_at = models.DateTimeField(auto_now=True)\n# For SQLAlchemy:\n# created_at = Column(DateTime, default=datetime.utcnow)\n# updated_at = Column(DateTime, onupdate=datetime.utcnow)\n", description: "Add Python timestamp guidance", ruleId: "DB-001" }];
1559
+ }
1560
+ if (f.file.endsWith(".rb")) {
1561
+ return [{ type: "append", filePath: f.file, content: "\n# GESF: Rails has built-in timestamps. Add to model:\n# create_table :your_table do |t|\n# t.timestamps\n# end\n", description: "Add Rails timestamp guidance", ruleId: "DB-001" }];
1562
+ }
1563
+ if (f.file.endsWith(".go")) {
1564
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Add audit timestamps to GORM models:\n// type YourModel struct {\n// ID uint `json:\"id\" gorm:\"primaryKey\"`\n// CreatedAt time.Time `json:\"created_at\"`\n// UpdatedAt time.Time `json:\"updated_at\"`\n// }\n", description: "Add Go timestamp guidance", ruleId: "DB-001" }];
1565
+ }
1566
+ if (f.file.endsWith(".java")) {
1567
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Add JPA audit timestamps:\n// @CreatedDate\n// @Column(name = \"created_at\", updatable = false)\n// private Instant createdAt;\n//\n// @LastModifiedDate\n// @Column(name = \"updated_at\")\n// private Instant updatedAt;\n", description: "Add Java JPA timestamp guidance", ruleId: "DB-001" }];
1568
+ }
1569
+ if (f.file.endsWith(".php")) {
1570
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Laravel uses timestamps() in migrations:\n// $table->timestamps(); // adds created_at, updated_at\n// $table->softDeletes(); // adds deleted_at\n", description: "Add Laravel timestamp guidance", ruleId: "DB-001" }];
1571
+ }
1572
+ if (f.file.endsWith(".rs")) {
1573
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Add audit timestamps to ORM models:\n// Diesel: created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n// updated_at TIMESTAMP NOT NULL DEFAULT NOW(),\n// SQLx: created_at: chrono::NaiveDateTime,\n// updated_at: chrono::NaiveDateTime,\n// SeaORM: created_at: DateTime,\n// updated_at: DateTime,\n", description: "Add Rust timestamp guidance", ruleId: "DB-001" }];
1574
+ }
1575
+ return [];
919
1576
  }
920
1577
  function buildSoftDeleteFix(root, f) {
921
- if (!f.file.endsWith(".prisma"))
922
- return [];
923
- const content = readFileSafe(path.join(root, f.file));
924
- if (!content)
925
- return [];
926
- const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
927
- if (!modelMatch || modelMatch.length === 0)
928
- return [];
929
- const block = modelMatch[0];
930
- const closingBrace = block.lastIndexOf("}");
931
- if (closingBrace === -1)
932
- return [];
933
- return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + "\n deletedAt DateTime?" + block.slice(closingBrace), description: "Add deletedAt to Prisma model", ruleId: "DB-002" }];
1578
+ if (f.file.endsWith(".prisma")) {
1579
+ const content = readFileSafe(path.join(root, f.file));
1580
+ if (!content)
1581
+ return [];
1582
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
1583
+ if (!modelMatch || modelMatch.length === 0)
1584
+ return [];
1585
+ const block = modelMatch[0];
1586
+ const closingBrace = block.lastIndexOf("}");
1587
+ if (closingBrace === -1)
1588
+ return [];
1589
+ return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + "\n deletedAt DateTime?" + block.slice(closingBrace), description: "Add deletedAt to Prisma model", ruleId: "DB-002" }];
1590
+ }
1591
+ if (f.file.endsWith(".py")) {
1592
+ return [{ type: "append", filePath: f.file, content: "\n# GESF: Add soft delete to Django/SQLAlchemy:\n# Django: deleted_at = models.DateTimeField(null=True, blank=True)\n# SQLAlchemy: deleted_at = Column(DateTime, nullable=True)\n", description: "Add Python soft delete guidance", ruleId: "DB-002" }];
1593
+ }
1594
+ if (f.file.endsWith(".go")) {
1595
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Add soft delete to GORM:\n// DeletedAt gorm.DeletedAt `json:\"deleted_at\" gorm:\"index\"`\n", description: "Add Go soft delete guidance", ruleId: "DB-002" }];
1596
+ }
1597
+ if (f.file.endsWith(".rs")) {
1598
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Add soft delete:\n// Diesel: deleted_at TIMESTAMP NULL,\n// SQLx: deleted_at: Option<chrono::NaiveDateTime>,\n// SeaORM: deleted_at: Option<DateTime>,\n", description: "Add Rust soft delete guidance", ruleId: "DB-002" }];
1599
+ }
1600
+ return [];
934
1601
  }
935
1602
  function buildUserAuditFix(root, f) {
936
- if (!f.file.endsWith(".prisma"))
937
- return [];
938
- const content = readFileSafe(path.join(root, f.file));
939
- if (!content)
940
- return [];
941
- const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
942
- if (!modelMatch || modelMatch.length === 0)
943
- return [];
944
- const block = modelMatch[0];
945
- const closingBrace = block.lastIndexOf("}");
946
- if (closingBrace === -1)
947
- return [];
948
- return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + "\n createdBy String?\n updatedBy String?" + block.slice(closingBrace), description: "Add createdBy/updatedBy columns", ruleId: "DB-003" }];
1603
+ if (f.file.endsWith(".prisma")) {
1604
+ const content = readFileSafe(path.join(root, f.file));
1605
+ if (!content)
1606
+ return [];
1607
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
1608
+ if (!modelMatch || modelMatch.length === 0)
1609
+ return [];
1610
+ const block = modelMatch[0];
1611
+ const closingBrace = block.lastIndexOf("}");
1612
+ if (closingBrace === -1)
1613
+ return [];
1614
+ return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + "\n createdBy String?\n updatedBy String?" + block.slice(closingBrace), description: "Add createdBy/updatedBy columns", ruleId: "DB-003" }];
1615
+ }
1616
+ if (f.file.endsWith(".py")) {
1617
+ return [{ type: "append", filePath: f.file, content: "\n# GESF: Add user audit columns:\n# Django: created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')\n# SQLAlchemy: created_by = Column(Integer, ForeignKey('users.id'))\n", description: "Add Python user audit guidance", ruleId: "DB-003" }];
1618
+ }
1619
+ if (f.file.endsWith(".rs")) {
1620
+ return [{ type: "append", filePath: f.file, content: "\n// GESF: Add user audit columns:\n// Diesel: created_by VARCHAR(255) NULL,\n// updated_by VARCHAR(255) NULL,\n// SQLx: created_by: Option<String>,\n// updated_by: Option<String>,\n", description: "Add Rust user audit guidance", ruleId: "DB-003" }];
1621
+ }
1622
+ return [];
949
1623
  }
950
1624
  function buildAuditModelFix(root) {
951
- const content = readFileSafe(path.join(root, "prisma/schema.prisma"));
952
- if (!content)
953
- return [];
954
- return [{ type: "append", filePath: "prisma/schema.prisma", content: "\\nmodel Audit {\\n id Int @id @default(autoincrement())\\n userId String\\n action String\\n resource String\\n timestamp DateTime @default(now())\\n ipAddress String\\n metadata Json?\\n}\\n", description: "Add Audit model to Prisma schema", ruleId: "DB-004" }];
1625
+ if (fs.existsSync(path.join(root, "prisma/schema.prisma"))) {
1626
+ return [{ type: "append", filePath: "prisma/schema.prisma", content: "\\nmodel Audit {\\n id Int @id @default(autoincrement())\\n userId String\\n action String\\n resource String\\n timestamp DateTime @default(now())\\n ipAddress String\\n metadata Json?\\n}\\n", description: "Add Audit model to Prisma schema", ruleId: "DB-004" }];
1627
+ }
1628
+ const lang = detectProjectLanguage(root);
1629
+ if (lang === "python") {
1630
+ return [{ type: "create", filePath: "lib/models/audit.py", content: `from datetime import datetime\nfrom sqlalchemy import Column, Integer, String, DateTime, JSON\nfrom sqlalchemy.ext.declarative import declarative_base\n\nBase = declarative_base()\n\nclass Audit(Base):\n __tablename__ = 'audit'\n id = Column(Integer, primary_key=True, autoincrement=True)\n user_id = Column(String(255))\n action = Column(String(255))\n resource = Column(String(255))\n timestamp = Column(DateTime, default=datetime.utcnow)\n ip_address = Column(String(45))\n metadata = Column(JSON)\n`, description: "Create Python Audit model (SQLAlchemy)", ruleId: "DB-004" }];
1631
+ }
1632
+ if (lang === "go") {
1633
+ return [{ type: "create", filePath: "lib/models/audit.go", content: `package models\n\nimport "time"\n\ntype Audit struct {\n\tID uint \`json:"id" gorm:"primaryKey;autoIncrement"\`\n\tUserID string \`json:"userId"\`\n\tAction string \`json:"action"\`\n\tResource string \`json:"resource"\`\n\tTimestamp time.Time \`json:"timestamp" gorm:"default:now()"\`\n\tIPAddress string \`json:"ipAddress"\`\n}\n`, description: "Create Go Audit model (GORM)", ruleId: "DB-004" }];
1634
+ }
1635
+ if (lang === "java") {
1636
+ return [{ type: "create", filePath: "src/main/java/com/example/Audit.java", content: `package com.example;\n\nimport jakarta.persistence.*;\nimport java.time.Instant;\n\n@Entity\n@Table(name = "audit")\npublic class Audit {\n @Id @GeneratedValue(strategy = GenerationType.IDENTITY)\n private Long id;\n private String userId;\n private String action;\n private String resource;\n private String ipAddress;\n @Column(columnDefinition = "jsonb")\n private String metadata;\n private Instant timestamp = Instant.now();\n}\n`, description: "Create Java Audit entity (JPA)", ruleId: "DB-004" }];
1637
+ }
1638
+ if (lang === "rust") {
1639
+ return [{ type: "create", filePath: "src/models/audit.rs", content: `use chrono::NaiveDateTime;
1640
+
1641
+ #[derive(Debug, Queryable, Serialize)]
1642
+ pub struct Audit {
1643
+ pub id: i32,
1644
+ pub user_id: String,
1645
+ pub action: String,
1646
+ pub resource: String,
1647
+ pub ip_address: String,
1648
+ pub timestamp: NaiveDateTime,
1649
+ }
1650
+
1651
+ // Diesel table definition:
1652
+ // table! {
1653
+ // audit (id) {
1654
+ // id -> Int4,
1655
+ // user_id -> Varchar,
1656
+ // action -> Varchar,
1657
+ // resource -> Varchar,
1658
+ // ip_address -> Varchar,
1659
+ // timestamp -> Timestamp,
1660
+ // }
1661
+ // }
1662
+ `, description: "Create Rust Audit model (Diesel)", ruleId: "DB-004" }];
1663
+ }
1664
+ return [];
955
1665
  }
956
1666
  function getNpmInstallsFromActions(actions) {
957
1667
  const installs = new Set();
@@ -972,6 +1682,36 @@ function getNpmInstallsFromActions(actions) {
972
1682
  return [...installs];
973
1683
  }
974
1684
  function buildEncryptionAtRestImpl(root, hasSrc) {
1685
+ const lang = detectProjectLanguage(root);
1686
+ if (lang === "rust") {
1687
+ return [
1688
+ { type: "create", filePath: "src/encryption.rs", content: `use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
1689
+ use aes_gcm::aead::Aead;
1690
+ use rand::RngCore;
1691
+ use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
1692
+
1693
+ pub fn encrypt(plaintext: &str, key: &[u8; 32]) -> Result<String, aes_gcm::Error> {
1694
+ let cipher = Aes256Gcm::new(key.into());
1695
+ let mut nonce_bytes = [0u8; 12];
1696
+ rand::thread_rng().fill_bytes(&mut nonce_bytes);
1697
+ let nonce = Nonce::from_slice(&nonce_bytes);
1698
+ let ciphertext = cipher.encrypt(nonce, plaintext.as_bytes())?;
1699
+ let mut combined = nonce_bytes.to_vec();
1700
+ combined.extend_from_slice(&ciphertext);
1701
+ Ok(BASE64.encode(&combined))
1702
+ }
1703
+
1704
+ pub fn decrypt(encoded: &str, key: &[u8; 32]) -> Result<String, aes_gcm::Error> {
1705
+ let combined = BASE64.decode(encoded).map_err(|_| aes_gcm::Error)?;
1706
+ let (nonce_bytes, ciphertext) = combined.split_at(12);
1707
+ let cipher = Aes256Gcm::new(key.into());
1708
+ let nonce = Nonce::from_slice(nonce_bytes);
1709
+ let plaintext = cipher.decrypt(nonce, ciphertext)?;
1710
+ String::from_utf8(plaintext).map_err(|_| aes_gcm::Error)
1711
+ }
1712
+ `, description: "Create Rust AES-256-GCM encryption utility", ruleId: "GDPR-ART32-002" },
1713
+ ];
1714
+ }
975
1715
  const cryptoPath = hasSrc ? "src/lib/encryption.ts" : "lib/encryption.ts";
976
1716
  return [
977
1717
  { type: "npm-install", filePath: "package.json", description: "Node.js crypto is built-in", ruleId: "GDPR-ART32-002" },
@@ -979,14 +1719,46 @@ function buildEncryptionAtRestImpl(root, hasSrc) {
979
1719
  ];
980
1720
  }
981
1721
  function buildEncryptionInTransitImpl(root, _hasSrc) {
1722
+ const lang = detectProjectLanguage(root);
982
1723
  const appFile = findMainAppFile(root);
983
1724
  const actions = [];
1725
+ if (lang === "rust") {
1726
+ if (appFile) {
1727
+ actions.push({ type: "append", filePath: appFile, content: "\n// GESF: Enforce TLS in production\n// Use a reverse proxy (nginx, caddy) for TLS termination\n// or configure rustls with your certificate:\n// let config = rustls::ServerConfig::builder()\n// .with_safe_defaults()\n// .with_no_client_auth()\n// .with_single_cert(certs, key);\n", description: "Add Rust TLS guidance", ruleId: "GDPR-ART32-003" });
1728
+ }
1729
+ return actions;
1730
+ }
984
1731
  if (appFile) {
985
1732
  actions.push({ type: "append", filePath: appFile, content: "\nif (process.env.NODE_ENV === 'production') {\n app.use((req, res, next) => {\n if (req.headers['x-forwarded-proto'] === 'http') {\n return res.redirect(301, `https://${req.headers.host}${req.url}`);\n }\n next();\n });\n}\n", description: "Add HTTPS redirect middleware", ruleId: "GDPR-ART32-003" });
986
1733
  }
987
1734
  return actions;
988
1735
  }
989
1736
  function buildUserIdentificationImpl(root, hasSrc) {
1737
+ const lang = detectProjectLanguage(root);
1738
+ if (lang === "rust") {
1739
+ const authPath = "src/auth.rs";
1740
+ if (fs.existsSync(path.join(root, authPath)))
1741
+ return [];
1742
+ return [
1743
+ { type: "create", filePath: authPath, content: `use argon2::{Argon2, Algorithm, Version, Params};
1744
+ use argon2::password_hash::{SaltString, PasswordHasher, PasswordVerifier};
1745
+ use rand::rngs::OsRng;
1746
+
1747
+ pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
1748
+ let salt = SaltString::generate(&mut OsRng);
1749
+ let params = Params::new(65536, 3, 4, Some(32))?;
1750
+ let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
1751
+ let hash = argon2.hash_password(password.as_bytes(), &salt)?;
1752
+ Ok(hash.to_string())
1753
+ }
1754
+
1755
+ pub fn verify_password(hash: &str, password: &str) -> Result<bool, argon2::password_hash::Error> {
1756
+ let parsed = argon2::PasswordHash::new(hash)?;
1757
+ Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
1758
+ }
1759
+ `, description: "Create Rust auth utility with Argon2id", ruleId: "GDPR-ART32-004" },
1760
+ ];
1761
+ }
990
1762
  const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
991
1763
  if (fs.existsSync(path.join(root, authPath)))
992
1764
  return [];
@@ -996,6 +1768,29 @@ function buildUserIdentificationImpl(root, hasSrc) {
996
1768
  ];
997
1769
  }
998
1770
  function buildIntegrityControlsImpl(root, hasSrc) {
1771
+ const lang = detectProjectLanguage(root);
1772
+ if (lang === "rust") {
1773
+ return [
1774
+ { type: "create", filePath: "src/integrity.rs", content: `use sha2::{Sha256, Digest};
1775
+
1776
+ pub fn hash_data(data: &str) -> String {
1777
+ let mut hasher = Sha256::new();
1778
+ hasher.update(data.as_bytes());
1779
+ format!("{:x}", hasher.finalize())
1780
+ }
1781
+
1782
+ pub fn verify_integrity(data: &str, expected_hash: &str) -> bool {
1783
+ hash_data(data) == expected_hash
1784
+ }
1785
+
1786
+ pub fn generate_checksum(content: &[u8]) -> String {
1787
+ let mut hasher = Sha256::new();
1788
+ hasher.update(content);
1789
+ format!("{:x}", hasher.finalize())
1790
+ }
1791
+ `, description: "Create Rust integrity verification utility", ruleId: "GDPR-ART32-007" },
1792
+ ];
1793
+ }
999
1794
  const integrityPath = hasSrc ? "src/lib/integrity.ts" : "lib/integrity.ts";
1000
1795
  return [
1001
1796
  { type: "create", filePath: integrityPath, content: `import { createHash } from 'node:crypto';\n\nexport function hashData(data: string): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\nexport function verifyIntegrity(data: string, expectedHash: string): boolean {\n return hashData(data) === expectedHash;\n}\n\nexport function generateChecksum(content: string): string {\n return createHash('sha256').update(content).digest('base64');\n}\n`, description: "Create integrity verification utility", ruleId: "GDPR-ART32-007" },
@@ -1007,9 +1802,13 @@ function buildBackupPolicyImpl(root, _hasSrc) {
1007
1802
  ];
1008
1803
  }
1009
1804
  function buildSecurityTestingImpl(root) {
1010
- const ghDir = path.join(root, ".github/workflows");
1805
+ const lang = detectProjectLanguage(root);
1806
+ const setupSteps = lang === "rust"
1807
+ ? ` - uses: actions-rs/toolchain@v1\n with:\n toolchain: stable\n - run: cargo build\n - name: cargo audit\n run: cargo install cargo-audit && cargo audit`
1808
+ : ` - uses: actions/setup-node@v4\n with:\n node-version: '22'\n - run: npm ci\n - name: npm audit\n run: npm audit --audit-level=high\n continue-on-error: true`;
1011
1809
  return [
1012
- { type: "create", filePath: ".github/workflows/security-scan.yml", content: `name: Security Scan\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\n schedule:\n - cron: '0 6 * * 1'\n\njobs:\n security:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '22'\n - run: npm ci\n - name: npm audit\n run: npm audit --audit-level=high\n continue-on-error: true\n - name: Run GESF compliance check\n run: npx @greenarmor/ges audit --ci\n`, description: "Create security scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" },
1810
+ { type: "create", filePath: ".github/workflows/security-scan.yml", content: `name: Security Scan\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\n schedule:\n - cron: '0 6 * * 1'\n\njobs:\n security:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n${setupSteps}\n - name: Run GESF compliance check\n run: npx @greenarmor/ges audit --ci\n`, description: "Create security scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" },
1811
+ { type: "create", filePath: ".github/workflows/sbom-scan.yml", content: `name: SBOM Generation & Scan\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\n schedule:\n - cron: '0 6 * * 1'\n\njobs:\n sbom:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Generate SBOM with Syft\n uses: anchore/sbom-action@v0\n with:\n image: \"\"\n path: .\n format: cyclonedx-json\n output-file: sbom.json\n fail-build: false\n\n - name: Scan SBOM for vulnerabilities with Grype\n uses: anchore/scan-action@v6\n with:\n sbom: sbom.json\n fail-build: true\n severity-cutoff: high\n\n - name: Upload SBOM artifacts\n if: always()\n uses: actions/upload-artifact@v4\n with:\n name: sbom-artifacts\n path: sbom.json\n retention-days: 90\n`, description: "Create SBOM generation and scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" },
1013
1812
  ];
1014
1813
  }
1015
1814
  function generateDataInventory(projectName, projectType) {