@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/bundle/server.js CHANGED
@@ -326,12 +326,13 @@ function createArticle32Controls() {
326
326
  article: "Article 32(1)(d)",
327
327
  status: "not-implemented",
328
328
  severity: "critical",
329
- implementation_guidance: "Run dependency scans (Trivy, Dependabot). Perform secret scanning (Gitleaks). Use SAST (Semgrep). Schedule penetration tests.",
329
+ implementation_guidance: "Run dependency scans (Trivy, Dependabot). Perform secret scanning (Gitleaks). Use SAST (Semgrep). Schedule penetration tests. Generate SBOM for supply-chain visibility.",
330
330
  checks: [
331
331
  { id: "GDPR-ART32-009-C1", description: "Dependency scanning in CI/CD", status: "not-implemented" },
332
332
  { id: "GDPR-ART32-009-C2", description: "Secret scanning in CI/CD", status: "not-implemented" },
333
333
  { id: "GDPR-ART32-009-C3", description: "SAST analysis integrated", status: "not-implemented" },
334
- { id: "GDPR-ART32-009-C4", description: "Penetration test schedule defined", status: "not-implemented" }
334
+ { id: "GDPR-ART32-009-C4", description: "Penetration test schedule defined", status: "not-implemented" },
335
+ { id: "GDPR-ART32-009-C5", description: "SBOM generated and scanned for vulnerabilities", status: "not-implemented" }
335
336
  ]
336
337
  }
337
338
  ];
@@ -855,15 +856,18 @@ function createCISPolicyPack() {
855
856
  {
856
857
  id: "CIS-002",
857
858
  name: "Inventory of Authorized and Unauthorized Software",
858
- description: "Maintain a software inventory.",
859
+ description: "Maintain a software inventory via SBOM generation and scanning.",
859
860
  category: "asset-management",
860
861
  framework: "CIS",
861
862
  status: "not-implemented",
862
863
  severity: "high",
863
- implementation_guidance: "Use package managers and lock files. Scan for unauthorized software. Maintain SBOM.",
864
+ implementation_guidance: "Generate SBOM in CycloneDX or SPDX format using Syft or Trivy. Scan SBOM for vulnerabilities using Grype. Automate SBOM generation in CI/CD pipeline. Store SBOM artifacts alongside release artifacts.",
864
865
  checks: [
865
866
  { id: "CIS-002-C1", description: "Software inventory (SBOM) maintained", status: "not-implemented" },
866
- { id: "CIS-002-C2", description: "Dependency scanning implemented", status: "not-implemented" }
867
+ { id: "CIS-002-C2", description: "Dependency scanning implemented", status: "not-implemented" },
868
+ { id: "CIS-002-C3", description: "SBOM generated in CycloneDX or SPDX format", status: "not-implemented" },
869
+ { id: "CIS-002-C4", description: "SBOM vulnerability scanning configured", status: "not-implemented" },
870
+ { id: "CIS-002-C5", description: "SBOM generation automated in CI/CD", status: "not-implemented" }
867
871
  ]
868
872
  },
869
873
  {
@@ -1015,6 +1019,21 @@ function createNISTPolicyPack() {
1015
1019
  { id: "NIST-RC-001-C2", description: "RTO and RPO defined", status: "not-implemented" },
1016
1020
  { id: "NIST-RC-001-C3", description: "Regular recovery tests", status: "not-implemented" }
1017
1021
  ]
1022
+ },
1023
+ {
1024
+ id: "NIST-ID-002",
1025
+ name: "Supply Chain Risk Management",
1026
+ description: "Identify and manage supply chain risks through software Bill of Materials (SBOM).",
1027
+ category: "identify",
1028
+ framework: "NIST",
1029
+ status: "not-implemented",
1030
+ severity: "high",
1031
+ implementation_guidance: "Generate SBOM for all software components using Syft or Trivy. Scan SBOM for known vulnerabilities using Grype. Automate SBOM generation in CI/CD. Enforce SBOM-based policies for third-party dependencies.",
1032
+ checks: [
1033
+ { id: "NIST-ID-002-C1", description: "SBOM generated for all dependencies", status: "not-implemented" },
1034
+ { id: "NIST-ID-002-C2", description: "SBOM vulnerability scanning automated", status: "not-implemented" },
1035
+ { id: "NIST-ID-002-C3", description: "Third-party dependency risk assessed", status: "not-implemented" }
1036
+ ]
1018
1037
  }
1019
1038
  ];
1020
1039
  return {
@@ -6416,7 +6435,7 @@ import * as path2 from "node:path";
6416
6435
  var __filename = url.fileURLToPath(import.meta.url);
6417
6436
  var __dirname = path2.dirname(__filename);
6418
6437
  var require2 = createRequire(import.meta.url);
6419
- var pkg = require2("../../package.json");
6438
+ var pkg = {"version":"0.6.1"};
6420
6439
  var GESF_VERSION = pkg.version;
6421
6440
 
6422
6441
  // src/server.ts
@@ -7115,12 +7134,156 @@ function applyAutoFixAction(root, action) {
7115
7134
  }
7116
7135
  }
7117
7136
  function findMainAppFile(root) {
7118
- 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"];
7119
- for (const c of candidates) {
7137
+ const lang = detectProjectLanguage(root);
7138
+ const candidates = {
7139
+ typescript: ["src/index.ts", "src/app.ts", "src/server.ts", "src/main.ts", "index.ts", "app.ts", "server.ts"],
7140
+ javascript: ["src/index.js", "src/app.js", "src/server.js", "src/main.js", "index.js", "app.js", "server.js"],
7141
+ python: ["app.py", "main.py", "manage.py", "wsgi.py", "asgi.py", "src/app.py", "src/main.py"],
7142
+ ruby: ["config.ru", "app.rb", "server.rb", "main.rb", "config/application.rb"],
7143
+ go: ["main.go", "cmd/server/main.go", "cmd/app/main.go"],
7144
+ java: ["src/main/java/com/example/Application.java", "src/main/java/Application.java"],
7145
+ php: ["public/index.php", "index.php", "app.php", "app/Http/Kernel.php"],
7146
+ rust: ["src/main.rs", "src/bin/main.rs", "src/app.rs"],
7147
+ csharp: ["Program.cs", "Startup.cs"]
7148
+ };
7149
+ const exts = candidates[lang] || [];
7150
+ for (const c of exts) {
7120
7151
  if (fs2.existsSync(path3.join(root, c))) return c;
7121
7152
  }
7153
+ if (lang === "java") {
7154
+ const found = findFileRecursive(root, "Application.java", "src/main/java");
7155
+ if (found) return found;
7156
+ }
7157
+ if (lang === "go") {
7158
+ for (const c of ["cmd/server/main.go", "cmd/app/main.go", "main.go"]) {
7159
+ if (fs2.existsSync(path3.join(root, c))) return c;
7160
+ }
7161
+ }
7122
7162
  return null;
7123
7163
  }
7164
+ function findFileRecursive(root, name, baseDir) {
7165
+ const dir = path3.join(root, baseDir);
7166
+ if (!fs2.existsSync(dir)) return null;
7167
+ try {
7168
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
7169
+ for (const e of entries) {
7170
+ if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "venv" || e.name === "__pycache__" || e.name === ".git") continue;
7171
+ const childPath = path3.join(baseDir, e.name);
7172
+ if (e.isDirectory()) {
7173
+ const found = findFileRecursive(root, name, childPath);
7174
+ if (found) return found;
7175
+ } else if (e.name === name) {
7176
+ return childPath;
7177
+ }
7178
+ }
7179
+ } catch {
7180
+ }
7181
+ return null;
7182
+ }
7183
+ function detectProjectLanguage(root) {
7184
+ if (fs2.existsSync(path3.join(root, "go.mod"))) return "go";
7185
+ if (fs2.existsSync(path3.join(root, "Cargo.toml"))) return "rust";
7186
+ if (fs2.existsSync(path3.join(root, "requirements.txt")) || fs2.existsSync(path3.join(root, "pyproject.toml")) || fs2.existsSync(path3.join(root, "Pipfile")) || fs2.existsSync(path3.join(root, "setup.py"))) return "python";
7187
+ if (fs2.existsSync(path3.join(root, "go.mod"))) return "go";
7188
+ if (fs2.existsSync(path3.join(root, "pom.xml")) || fs2.existsSync(path3.join(root, "build.gradle")) || fs2.existsSync(path3.join(root, "build.gradle.kts"))) return "java";
7189
+ if (fs2.existsSync(path3.join(root, "Gemfile"))) return "ruby";
7190
+ if (fs2.existsSync(path3.join(root, "composer.json"))) return "php";
7191
+ const pkgContent = readFileSafe(path3.join(root, "package.json"));
7192
+ if (pkgContent) {
7193
+ try {
7194
+ const pkg2 = JSON.parse(pkgContent);
7195
+ const deps = { ...pkg2.dependencies, ...pkg2.devDependencies };
7196
+ if (deps.typescript || deps["@types/node"] || fs2.existsSync(path3.join(root, "tsconfig.json"))) return "typescript";
7197
+ return "javascript";
7198
+ } catch {
7199
+ }
7200
+ }
7201
+ if (fs2.existsSync(path3.join(root, "tsconfig.json"))) return "typescript";
7202
+ return "javascript";
7203
+ }
7204
+ function detectWebFramework(root, lang) {
7205
+ if (lang === "typescript" || lang === "javascript") {
7206
+ if (hasDep(root, "express")) return "express";
7207
+ if (hasDep(root, "fastify")) return "fastify";
7208
+ if (hasDep(root, "koa")) return "koa";
7209
+ if (hasDep(root, "hono")) return "hono";
7210
+ if (hasDep(root, "next")) return "next";
7211
+ if (hasDep(root, "@nestjs/core")) return "nestjs";
7212
+ if (hasDep(root, "@sveltejs/kit")) return "sveltekit";
7213
+ }
7214
+ if (lang === "python") {
7215
+ const reqFiles = ["requirements.txt", "pyproject.toml", "Pipfile"];
7216
+ for (const f of reqFiles) {
7217
+ const c = readFileSafe(path3.join(root, f));
7218
+ if (c) {
7219
+ if (/^\s*django\b/mi.test(c) || /django/i.test(c)) return "django";
7220
+ if (/^\s*flask\b/mi.test(c) || /flask/i.test(c)) return "flask";
7221
+ if (/^\s*fastapi\b/mi.test(c) || /fastapi/i.test(c)) return "fastapi";
7222
+ if (/^\s*sanic\b/mi.test(c) || /sanic/i.test(c)) return "sanic";
7223
+ }
7224
+ }
7225
+ const settingsPy = readFileSafe(path3.join(root, "settings.py")) || readFileSafe(path3.join(root, "app/settings.py")) || readFileSafe(path3.join(root, "config/settings.py"));
7226
+ if (settingsPy && /DJANGO_SETTINGS_MODULE|INSTALLED_APPS|django/.test(settingsPy)) return "django";
7227
+ const appPy = readFileSafe(path3.join(root, "app.py")) || readFileSafe(path3.join(root, "main.py"));
7228
+ if (appPy) {
7229
+ if (/from\s+flask\s+import|import\s+flask/.test(appPy)) return "flask";
7230
+ if (/from\s+fastapi\s+import|import\s+fastapi/.test(appPy)) return "fastapi";
7231
+ if (/from\s+django/.test(appPy)) return "django";
7232
+ }
7233
+ }
7234
+ if (lang === "ruby") {
7235
+ const gemfile = readFileSafe(path3.join(root, "Gemfile"));
7236
+ if (gemfile) {
7237
+ if (/rails/i.test(gemfile)) return "rails";
7238
+ if (/sinatra/i.test(gemfile)) return "sinatra";
7239
+ }
7240
+ }
7241
+ if (lang === "go") {
7242
+ const goMod = readFileSafe(path3.join(root, "go.mod")) || "";
7243
+ const mainGo = readFileSafe(path3.join(root, "main.go")) || "";
7244
+ const allGo = goMod + mainGo;
7245
+ if (/gin-gonic|gin\.Default|gin\.New/.test(allGo)) return "gin";
7246
+ if (/fiber\.New/.test(allGo)) return "fiber";
7247
+ if (/echo\.New/.test(allGo)) return "echo";
7248
+ if (/chi\.NewRouter|chi\.Mux/.test(allGo)) return "chi";
7249
+ if (/mux\.NewRouter/.test(allGo)) return "gorilla";
7250
+ if (/http\.ListenAndServe|http\.HandleFunc/.test(allGo)) return "nethttp";
7251
+ }
7252
+ if (lang === "java") {
7253
+ const pom = readFileSafe(path3.join(root, "pom.xml")) || "";
7254
+ const gradle = readFileSafe(path3.join(root, "build.gradle")) || "";
7255
+ const all = pom + gradle;
7256
+ if (/spring-boot|springframework/.test(all)) return "spring";
7257
+ if (/ktor/.test(all)) return "ktor";
7258
+ if (/quarkus/.test(all)) return "quarkus";
7259
+ if (/micronaut/.test(all)) return "micronaut";
7260
+ }
7261
+ if (lang === "rust") {
7262
+ const cargo = readFileSafe(path3.join(root, "Cargo.toml")) || "";
7263
+ const mainRs = readFileSafe(path3.join(root, "src/main.rs")) || "";
7264
+ const libRs = readFileSafe(path3.join(root, "src/lib.rs")) || "";
7265
+ const all = cargo + mainRs + libRs;
7266
+ if (/actix-web|actix_web/.test(all)) return "actix";
7267
+ if (/axum/.test(all)) return "axum";
7268
+ if (/rocket/.test(all)) return "rocket";
7269
+ if (/warp/.test(all)) return "warp";
7270
+ }
7271
+ if (lang === "php") {
7272
+ const composer = readFileSafe(path3.join(root, "composer.json"));
7273
+ if (composer) {
7274
+ try {
7275
+ const pkg2 = JSON.parse(composer);
7276
+ const req = pkg2.require || {};
7277
+ if (req["laravel/framework"]) return "laravel";
7278
+ if (req["symfony/symfony"] || req["symfony/framework-bundle"]) return "symfony";
7279
+ if (req["slim/slim"]) return "slim";
7280
+ if (req["laravel/lumen-framework"]) return "lumen";
7281
+ } catch {
7282
+ }
7283
+ }
7284
+ }
7285
+ return "generic";
7286
+ }
7124
7287
  function hasDep(root, dep) {
7125
7288
  const pkg2 = readJsonFileSafe(path3.join(root, "package.json"));
7126
7289
  if (!pkg2) return false;
@@ -7135,33 +7298,270 @@ function readFileSafe(filePath) {
7135
7298
  }
7136
7299
  }
7137
7300
  function buildHelmetFix(root) {
7138
- const appFile = findMainAppFile(root);
7139
- if (!appFile) return [];
7140
- const actions = [
7141
- { type: "npm-install", filePath: "package.json", description: "Install helmet", ruleId: "CONFIG-001" }
7142
- ];
7143
- const content = readFileSafe(path3.join(root, appFile));
7144
- if (content && content.includes("const app = express()")) {
7145
- 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" });
7146
- } else {
7147
- actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'helmet';\napp.use(helmet());\n", description: "Add helmet import and middleware", ruleId: "CONFIG-001" });
7301
+ const lang = detectProjectLanguage(root);
7302
+ const fw = detectWebFramework(root, lang);
7303
+ const actions = [];
7304
+ if (lang === "typescript" || lang === "javascript") {
7305
+ const appFile = findMainAppFile(root);
7306
+ if (!appFile) return [];
7307
+ if (fw === "express") {
7308
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install helmet", ruleId: "CONFIG-001" });
7309
+ const content = readFileSafe(path3.join(root, appFile));
7310
+ if (content && content.includes("const app = express()")) {
7311
+ 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" });
7312
+ } else {
7313
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'helmet';\napp.use(helmet());\n", description: "Add helmet import and middleware", ruleId: "CONFIG-001" });
7314
+ }
7315
+ } else if (fw === "fastify") {
7316
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install @fastify/helmet", ruleId: "CONFIG-001" });
7317
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from '@fastify/helmet';\napp.register(helmet);\n", description: "Add Fastify helmet plugin", ruleId: "CONFIG-001" });
7318
+ } else if (fw === "koa") {
7319
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install koa-helmet", ruleId: "CONFIG-001" });
7320
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'koa-helmet';\napp.use(helmet());\n", description: "Add koa-helmet middleware", ruleId: "CONFIG-001" });
7321
+ } else if (fw === "hono") {
7322
+ 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" });
7323
+ }
7324
+ } else if (lang === "python") {
7325
+ if (fw === "django") {
7326
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Python uses django-csp/secure", ruleId: "CONFIG-001" });
7327
+ const settingsFile = findFileRecursive(root, "settings.py", ".") || "settings.py";
7328
+ 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" });
7329
+ } else if (fw === "flask" || fw === "fastapi" || fw === "sanic") {
7330
+ const appFile = findMainAppFile(root) || "app.py";
7331
+ actions.push({
7332
+ type: "append",
7333
+ filePath: appFile,
7334
+ content: fw === "fastapi" ? "\nfrom fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware\napp.add_middleware(HTTPSRedirectMiddleware)\n" : "\nfrom flask_talisman import Talisman\nTalisman(app, force_https=True, strict_transport_security=True, session_cookie_secure=True)\n",
7335
+ description: `Add security headers for ${fw}`,
7336
+ ruleId: "CONFIG-001"
7337
+ });
7338
+ }
7339
+ } else if (lang === "ruby") {
7340
+ if (fw === "rails") {
7341
+ const envFile = fs2.existsSync(path3.join(root, "config/environments/production.rb")) ? "config/environments/production.rb" : "config/application.rb";
7342
+ 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" });
7343
+ }
7344
+ } else if (lang === "go") {
7345
+ const appFile = findMainAppFile(root) || "main.go";
7346
+ if (fw === "gin" || fw === "echo" || fw === "fiber" || fw === "chi" || fw === "nethttp") {
7347
+ actions.push({ type: "append", filePath: appFile, content: `
7348
+ import "net/http"
7349
+
7350
+ // Security headers middleware
7351
+ func securityHeaders(next http.Handler) http.Handler {
7352
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7353
+ w.Header().Set("X-Content-Type-Options", "nosniff")
7354
+ w.Header().Set("X-Frame-Options", "DENY")
7355
+ w.Header().Set("X-XSS-Protection", "1; mode=block")
7356
+ w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
7357
+ w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
7358
+ w.Header().Set("Content-Security-Policy", "default-src 'self'")
7359
+ next.ServeHTTP(w, r)
7360
+ })
7361
+ }
7362
+ `, description: "Add Go security headers middleware", ruleId: "CONFIG-001" });
7363
+ }
7364
+ } else if (lang === "java") {
7365
+ if (fw === "spring") {
7366
+ const hasSrc = fs2.existsSync(path3.join(root, "src/main/java"));
7367
+ const configPath = hasSrc ? "src/main/java/com/example/SecurityConfig.java" : "SecurityConfig.java";
7368
+ actions.push({ type: "create", filePath: configPath, content: `import org.springframework.context.annotation.Bean;
7369
+ import org.springframework.context.annotation.Configuration;
7370
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7371
+ import org.springframework.security.web.SecurityFilterChain;
7372
+ import org.springframework.security.web.header.writers.StaticHeadersWriter;
7373
+
7374
+ @Configuration
7375
+ public class SecurityConfig {
7376
+ @Bean
7377
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7378
+ http.headers()
7379
+ .contentSecurityPolicy("default-src 'self'")
7380
+ .and()
7381
+ .xssProtection()
7382
+ .and()
7383
+ .frameOptions().deny()
7384
+ .httpStrictTransportSecurity()
7385
+ .includeSubDomains(true)
7386
+ .preload(true)
7387
+ .maxAgeInSeconds(31536000);
7388
+ return http.build();
7389
+ }
7390
+ }
7391
+ `, description: "Create Spring Security config with headers", ruleId: "CONFIG-001" });
7392
+ }
7393
+ } else if (lang === "php") {
7394
+ if (fw === "laravel" || fw === "symfony") {
7395
+ const middleware = fw === "laravel" ? "app/Http/Middleware/SecurityHeaders.php" : "src/Middleware/SecurityHeadersMiddleware.php";
7396
+ const content = fw === "laravel" ? `<?php
7397
+
7398
+ namespace App\\Http\\Middleware;
7399
+
7400
+ use Closure;
7401
+
7402
+ class SecurityHeaders
7403
+ {
7404
+ public function handle($request, Closure $next)
7405
+ {
7406
+ $response = $next($request);
7407
+ $response->headers->set('X-Content-Type-Options', 'nosniff');
7408
+ $response->headers->set('X-Frame-Options', 'DENY');
7409
+ $response->headers->set('X-XSS-Protection', '1; mode=block');
7410
+ $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
7411
+ $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
7412
+ return $response;
7413
+ }
7414
+ }
7415
+ ` : `<?php
7416
+
7417
+ namespace App\\Middleware;
7418
+
7419
+ use Symfony\\Component\\HttpFoundation\\Response;
7420
+
7421
+ class SecurityHeadersMiddleware
7422
+ {
7423
+ public function __invoke($request, $handler)
7424
+ {
7425
+ $response = $handler->handle($request);
7426
+ $response->headers->set('X-Content-Type-Options', 'nosniff');
7427
+ $response->headers->set('X-Frame-Options', 'DENY');
7428
+ $response->headers->set('X-XSS-Protection', '1; mode=block');
7429
+ $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
7430
+ return $response;
7431
+ }
7432
+ }
7433
+ `;
7434
+ actions.push({ type: "create", filePath: middleware, content, description: `Create security headers middleware for ${fw}`, ruleId: "CONFIG-001" });
7435
+ }
7436
+ } else if (lang === "rust") {
7437
+ const appFile = findMainAppFile(root) || "src/main.rs";
7438
+ if (fw === "actix") {
7439
+ actions.push({ type: "create", filePath: "src/middleware/security_headers.rs", content: `use actix_web::{HttpResponse, dev::{ServiceRequest, Service, ServiceResponse}};
7440
+
7441
+ pub fn add_security_headers(res: &mut HttpResponse) {
7442
+ res.headers_mut().insert(("X-Content-Type-Options", "nosniff"));
7443
+ res.headers_mut().insert(("X-Frame-Options", "DENY"));
7444
+ res.headers_mut().insert(("X-XSS-Protection", "1; mode=block"));
7445
+ res.headers_mut().insert(("Strict-Transport-Security", "max-age=31536000; includeSubDomains"));
7446
+ res.headers_mut().insert(("Referrer-Policy", "strict-origin-when-cross-origin"));
7447
+ res.headers_mut().insert(("Content-Security-Policy", "default-src 'self'"));
7448
+ }
7449
+ `, description: "Create Actix-web security headers middleware", ruleId: "CONFIG-001" });
7450
+ } else if (fw === "axum") {
7451
+ actions.push({ type: "create", filePath: "src/middleware/security_headers.rs", content: `use axum::{http::HeaderValue, response::Response};
7452
+
7453
+ pub async fn security_headers(mut res: Response) -> Response {
7454
+ let headers = res.headers_mut();
7455
+ headers.insert("X-Content-Type-Options", HeaderValue::from_static("nosniff"));
7456
+ headers.insert("X-Frame-Options", HeaderValue::from_static("DENY"));
7457
+ headers.insert("X-XSS-Protection", HeaderValue::from_static("1; mode=block"));
7458
+ headers.insert("Strict-Transport-Security", HeaderValue::from_static("max-age=31536000; includeSubDomains"));
7459
+ headers.insert("Referrer-Policy", HeaderValue::from_static("strict-origin-when-cross-origin"));
7460
+ headers.insert("Content-Security-Policy", HeaderValue::from_static("default-src 'self'"));
7461
+ res
7462
+ }
7463
+ `, description: "Create Axum security headers middleware", ruleId: "CONFIG-001" });
7464
+ } else {
7465
+ 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" });
7466
+ }
7148
7467
  }
7149
7468
  return actions;
7150
7469
  }
7151
7470
  function buildCorsFix(root) {
7152
- const appFile = findMainAppFile(root);
7153
- if (!appFile) return [];
7154
- return [
7155
- { type: "npm-install", filePath: "package.json", description: "Install cors", ruleId: "CONFIG-002" },
7156
- { 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" }
7157
- ];
7471
+ const lang = detectProjectLanguage(root);
7472
+ const fw = detectWebFramework(root, lang);
7473
+ const actions = [];
7474
+ if (lang === "typescript" || lang === "javascript") {
7475
+ const appFile = findMainAppFile(root);
7476
+ if (!appFile) return [];
7477
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install cors", ruleId: "CONFIG-002" });
7478
+ if (fw === "fastify") {
7479
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install @fastify/cors", ruleId: "CONFIG-002" });
7480
+ 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" });
7481
+ } else {
7482
+ 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" });
7483
+ }
7484
+ } else if (lang === "python") {
7485
+ const appFile = findMainAppFile(root) || "app.py";
7486
+ if (fw === "django") {
7487
+ const settingsFile = findFileRecursive(root, "settings.py", ".") || "settings.py";
7488
+ 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" });
7489
+ } else if (fw === "fastapi") {
7490
+ 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" });
7491
+ } else if (fw === "flask") {
7492
+ 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" });
7493
+ } else {
7494
+ 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" });
7495
+ }
7496
+ } else if (lang === "ruby") {
7497
+ if (fw === "rails") {
7498
+ 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" });
7499
+ }
7500
+ } else if (lang === "go") {
7501
+ const appFile = findMainAppFile(root) || "main.go";
7502
+ actions.push({ type: "append", filePath: appFile, content: '\nimport "net/http"\n\nfunc corsMiddleware(allowedOrigins []string, next http.Handler) http.Handler {\n return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n origin := r.Header.Get("Origin")\n for _, o := range allowedOrigins {\n if origin == o {\n w.Header().Set("Access-Control-Allow-Origin", origin)\n w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")\n w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")\n break\n }\n }\n if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent); return }\n next.ServeHTTP(w, r)\n })\n}\n', description: "Add Go CORS middleware", ruleId: "CONFIG-002" });
7503
+ } else if (lang === "java") {
7504
+ if (fw === "spring") {
7505
+ actions.push({ type: "create", filePath: "src/main/java/com/example/CorsConfig.java", content: `import org.springframework.context.annotation.Bean;
7506
+ import org.springframework.context.annotation.Configuration;
7507
+ import org.springframework.web.cors.CorsConfiguration;
7508
+ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
7509
+ import org.springframework.web.filter.CorsFilter;
7510
+
7511
+ @Configuration
7512
+ public class CorsConfig {
7513
+ @Bean
7514
+ public CorsFilter corsFilter() {
7515
+ CorsConfiguration config = new CorsConfiguration();
7516
+ config.addAllowedOrigin("https://yourdomain.com");
7517
+ config.addAllowedHeader("*");
7518
+ config.addAllowedMethod("*");
7519
+ config.setAllowCredentials(true);
7520
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
7521
+ source.registerCorsConfiguration("/**", config);
7522
+ return new CorsFilter(source);
7523
+ }
7524
+ }
7525
+ `, description: "Create Spring CORS configuration", ruleId: "CONFIG-002" });
7526
+ }
7527
+ } else if (lang === "rust") {
7528
+ const appFile = findMainAppFile(root) || "src/main.rs";
7529
+ if (fw === "actix") {
7530
+ actions.push({ type: "create", filePath: "src/middleware/cors.rs", content: `use actix_cors::Cors;
7531
+ use actix_web::http::header;
7532
+
7533
+ pub fn cors_config() -> Cors {
7534
+ Cors::default()
7535
+ .allowed_origin("http://localhost:3000")
7536
+ .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
7537
+ .allowed_headers(vec![header::CONTENT_TYPE, header::AUTHORIZATION])
7538
+ .max_age(3600)
7539
+ }
7540
+ `, description: "Create Actix-web CORS configuration", ruleId: "CONFIG-002" });
7541
+ } else if (fw === "axum") {
7542
+ actions.push({ type: "create", filePath: "src/middleware/cors.rs", content: `use tower_http::cors::{CorsLayer, Any};
7543
+ use http::Method;
7544
+
7545
+ pub fn cors_layer() -> CorsLayer {
7546
+ CorsLayer::new()
7547
+ .allow_origin(["http://localhost:3000".parse().unwrap()])
7548
+ .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
7549
+ .allow_headers(Any)
7550
+ }
7551
+ `, description: "Create Axum CORS layer", ruleId: "CONFIG-002" });
7552
+ } else {
7553
+ 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" });
7554
+ }
7555
+ }
7556
+ return actions;
7158
7557
  }
7159
7558
  function buildEnvGitignoreFix(root) {
7160
7559
  const gi = fs2.existsSync(path3.join(root, ".gitignore")) ? ".gitignore" : null;
7560
+ const envFiles = detectProjectLanguage(root) === "python" ? "\n.env\n.env.*\n!.env.example\n*.pyc\n__pycache__/\n" : detectProjectLanguage(root) === "go" ? "\n.env\n.env.*\n!.env.example\n*.exe\n" : detectProjectLanguage(root) === "ruby" ? "\n.env\n.env.*\n!.env.example\n*.gem\n" : detectProjectLanguage(root) === "java" ? "\n.env\n.env.*\n!.env.example\n*.class\ntarget/\n" : detectProjectLanguage(root) === "php" ? "\n.env\n.env.*\n!.env.example\nvendor/\n" : detectProjectLanguage(root) === "rust" ? "\n.env\n.env.*\n!.env.example\ntarget/\n*.key\n*.pem\n" : "\n.env\n.env.*\n!.env.example\n";
7161
7561
  if (!gi) return buildGitignoreCreateFix(root);
7162
7562
  const content = readFileSafe(path3.join(root, gi)) || "";
7163
7563
  if (content.includes(".env")) return [];
7164
- return [{ type: "append", filePath: ".gitignore", content: "\n.env\n.env.*\n!.env.example\n", description: "Add .env to .gitignore", ruleId: "CONFIG-004" }];
7564
+ return [{ type: "append", filePath: ".gitignore", content: envFiles, description: "Add .env to .gitignore", ruleId: "CONFIG-004" }];
7165
7565
  }
7166
7566
  function buildDockerNonRootFix(root) {
7167
7567
  if (!fs2.existsSync(path3.join(root, "Dockerfile"))) return [];
@@ -7171,7 +7571,19 @@ function buildTLSFix(root, f) {
7171
7571
  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" }];
7172
7572
  }
7173
7573
  function buildGitignoreCreateFix(root) {
7174
- 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" }];
7574
+ const lang = detectProjectLanguage(root);
7575
+ const templates = {
7576
+ typescript: "node_modules/\n.env\n.env.*\n!.env.example\ndist/\nbuild/\n*.key\n*.pem\ncoverage/\n.DS_Store\n",
7577
+ javascript: "node_modules/\n.env\n.env.*\n!.env.example\ndist/\nbuild/\n*.key\n*.pem\ncoverage/\n.DS_Store\n",
7578
+ 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",
7579
+ ruby: ".env\n.env.*\n!.env.example\n*.key\n*.pem\nlog/\ntmp/\n*.gem\n.DS_Store\n",
7580
+ go: ".env\n.env.*\n!.env.example\n*.key\n*.pem\n*.exe\n/bin/\n.DS_Store\n",
7581
+ java: ".env\n.env.*\n!.env.example\n*.key\n*.pem\n*.class\ntarget/\n.idea/\n*.iml\n.DS_Store\n",
7582
+ php: ".env\n.env.*\n!.env.example\nvendor/\n*.key\n*.pem\n.DS_Store\n",
7583
+ rust: "target/\nCargo.lock\n.env\n.env.*\n!.env.example\n*.key\n*.pem\n.DS_Store\n",
7584
+ csharp: ".env\n.env.*\n!.env.example\nbin/\nobj/\n*.key\n*.pem\n.DS_Store\n"
7585
+ };
7586
+ return [{ type: "create", filePath: ".gitignore", content: templates[lang] || templates.javascript, description: `Create .gitignore for ${lang} project`, ruleId: "CONFIG-008" }];
7175
7587
  }
7176
7588
  function buildGitignoreEntryFix(root, f) {
7177
7589
  const entry = f.fix.replace("Add ", "").replace(" to .gitignore.", "");
@@ -7181,12 +7593,13 @@ ${entry}
7181
7593
  `, description: `Add ${entry} to .gitignore`, ruleId: "CONFIG-009" }];
7182
7594
  }
7183
7595
  function buildLoggingFix(root) {
7184
- const appFile = findMainAppFile(root);
7185
- const hasSrc = fs2.existsSync(path3.join(root, "src"));
7186
- const loggerPath = hasSrc ? "src/lib/logger.ts" : "lib/logger.ts";
7187
- const actions = [
7188
- { type: "npm-install", filePath: "package.json", description: "Install pino logger", ruleId: "CONFIG-010" },
7189
- { type: "create", filePath: loggerPath, content: `import pino from 'pino';
7596
+ const lang = detectProjectLanguage(root);
7597
+ const actions = [];
7598
+ if (lang === "typescript" || lang === "javascript") {
7599
+ const hasSrc = fs2.existsSync(path3.join(root, "src"));
7600
+ const loggerPath = hasSrc ? "src/lib/logger.ts" : "lib/logger.ts";
7601
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install pino logger", ruleId: "CONFIG-010" });
7602
+ actions.push({ type: "create", filePath: loggerPath, content: `import pino from 'pino';
7190
7603
 
7191
7604
  const logger = pino({
7192
7605
  level: process.env.LOG_LEVEL || 'info',
@@ -7206,12 +7619,171 @@ export function auditLog(params: AuditLogParams): void {
7206
7619
  }
7207
7620
 
7208
7621
  export default logger;
7209
- `, description: "Create structured logger with audit logging", ruleId: "CONFIG-010" }
7210
- ];
7211
- if (appFile) {
7212
- actions.push({ type: "append", filePath: appFile, content: `
7213
- import logger from './${hasSrc ? "lib/logger" : hasSrc ? "src/lib/logger" : "lib/logger"}';
7214
- `, description: "Import logger", ruleId: "CONFIG-010" });
7622
+ `, description: "Create structured logger with audit logging", ruleId: "CONFIG-010" });
7623
+ } else if (lang === "python") {
7624
+ actions.push({ type: "create", filePath: "lib/logger.py", content: `import logging
7625
+ import json
7626
+ from datetime import datetime
7627
+
7628
+ logger = logging.getLogger("audit")
7629
+ logger.setLevel(logging.INFO)
7630
+
7631
+ handler = logging.StreamHandler()
7632
+ handler.setFormatter(logging.Formatter('%(message)s'))
7633
+ logger.addHandler(handler)
7634
+
7635
+ def audit_log(user_id: str, action: str, resource: str, ip_address: str, **metadata):
7636
+ entry = {
7637
+ "userId": user_id,
7638
+ "action": action,
7639
+ "resource": resource,
7640
+ "ipAddress": ip_address,
7641
+ "timestamp": datetime.utcnow().isoformat() + "Z",
7642
+ "type": "audit",
7643
+ **metadata,
7644
+ }
7645
+ logger.info(json.dumps(entry))
7646
+ `, description: "Create Python audit logger", ruleId: "CONFIG-010" });
7647
+ } else if (lang === "ruby") {
7648
+ actions.push({ type: "create", filePath: "lib/audit_logger.rb", content: `require 'logger'
7649
+ require 'json'
7650
+
7651
+ class AuditLogger
7652
+ def initialize(logdev = $stdout)
7653
+ @logger = Logger.new(logdev)
7654
+ @logger.formatter = proc { |_, _, _, msg| msg }
7655
+ end
7656
+
7657
+ def audit_log(user_id:, action:, resource:, ip_address:, **metadata)
7658
+ entry = {
7659
+ userId: user_id,
7660
+ action: action,
7661
+ resource: resource,
7662
+ ipAddress: ip_address,
7663
+ timestamp: Time.now.utc.iso8601,
7664
+ type: 'audit',
7665
+ **metadata,
7666
+ }
7667
+ @logger.info(entry.to_json)
7668
+ end
7669
+ end
7670
+
7671
+ AUDIT = AuditLogger.new
7672
+ `, description: "Create Ruby audit logger", ruleId: "CONFIG-010" });
7673
+ } else if (lang === "go") {
7674
+ actions.push({ type: "create", filePath: "lib/audit.go", content: `package lib
7675
+
7676
+ import (
7677
+ "encoding/json"
7678
+ "log"
7679
+ "os"
7680
+ "time"
7681
+ )
7682
+
7683
+ type AuditEntry struct {
7684
+ UserID string "json:\\"userId\\""
7685
+ Action string "json:\\"action\\""
7686
+ Resource string "json:\\"resource\\""
7687
+ IPAddress string "json:\\"ipAddress\\""
7688
+ Timestamp string "json:\\"timestamp\\""
7689
+ Type string "json:\\"type\\""
7690
+ Metadata map[string]interface{} "json:\\"metadata,omitempty\\""
7691
+ }
7692
+
7693
+ var auditLogger = log.New(os.Stdout, "", 0)
7694
+
7695
+ func AuditLog(userID, action, resource, ipAddr string, metadata map[string]interface{}) {
7696
+ entry := AuditEntry{
7697
+ UserID: userID,
7698
+ Action: action,
7699
+ Resource: resource,
7700
+ IPAddress: ipAddr,
7701
+ Timestamp: time.Now().UTC().Format(time.RFC3339),
7702
+ Type: "audit",
7703
+ Metadata: metadata,
7704
+ }
7705
+ data, _ := json.Marshal(entry)
7706
+ auditLogger.Println(string(data))
7707
+ }
7708
+ `, description: "Create Go audit logger", ruleId: "CONFIG-010" });
7709
+ } else if (lang === "java") {
7710
+ actions.push({ type: "create", filePath: "src/main/java/com/example/AuditLogger.java", content: `package com.example;
7711
+
7712
+ import com.fasterxml.jackson.databind.ObjectMapper;
7713
+ import org.slf4j.Logger;
7714
+ import org.slf4j.LoggerFactory;
7715
+ import java.time.Instant;
7716
+ import java.util.Map;
7717
+
7718
+ public class AuditLogger {
7719
+ private static final Logger logger = LoggerFactory.getLogger("audit");
7720
+ private static final ObjectMapper mapper = new ObjectMapper();
7721
+
7722
+ public static void auditLog(String userId, String action, String resource, String ipAddress, Map<String, Object> metadata) {
7723
+ try {
7724
+ Map<String, Object> entry = Map.of(
7725
+ "userId", userId,
7726
+ "action", action,
7727
+ "resource", resource,
7728
+ "ipAddress", ipAddress,
7729
+ "timestamp", Instant.now().toString(),
7730
+ "type", "audit"
7731
+ );
7732
+ if (metadata != null) entry.putAll(metadata);
7733
+ logger.info(mapper.writeValueAsString(entry));
7734
+ } catch (Exception e) {
7735
+ logger.error("Audit log failed", e);
7736
+ }
7737
+ }
7738
+ }
7739
+ `, description: "Create Java audit logger", ruleId: "CONFIG-010" });
7740
+ } else if (lang === "php") {
7741
+ actions.push({ type: "create", filePath: "lib/audit_logger.php", content: `<?php
7742
+
7743
+ class AuditLogger
7744
+ {
7745
+ public static function log(string $userId, string $action, string $resource, string $ipAddress, array $metadata = []): void
7746
+ {
7747
+ $entry = array_merge([
7748
+ 'userId' => $userId,
7749
+ 'action' => $action,
7750
+ 'resource' => $resource,
7751
+ 'ipAddress' => $ipAddress,
7752
+ 'timestamp' => gmdate('c'),
7753
+ 'type' => 'audit',
7754
+ ], $metadata);
7755
+ error_log(json_encode($entry));
7756
+ }
7757
+ }
7758
+ `, description: "Create PHP audit logger", ruleId: "CONFIG-010" });
7759
+ } else if (lang === "rust") {
7760
+ actions.push({ type: "create", filePath: "src/logger.rs", content: `use serde_json::json;
7761
+ use tracing::{info, instrument};
7762
+ use chrono::Utc;
7763
+
7764
+ #[derive(Debug, serde::Serialize)]
7765
+ pub struct AuditEntry {
7766
+ pub user_id: String,
7767
+ pub action: String,
7768
+ pub resource: String,
7769
+ pub ip_address: String,
7770
+ pub timestamp: String,
7771
+ #[serde(rename = "type")]
7772
+ pub entry_type: String,
7773
+ }
7774
+
7775
+ pub fn audit_log(user_id: &str, action: &str, resource: &str, ip_address: &str) {
7776
+ let entry = AuditEntry {
7777
+ user_id: user_id.to_string(),
7778
+ action: action.to_string(),
7779
+ resource: resource.to_string(),
7780
+ ip_address: ip_address.to_string(),
7781
+ timestamp: Utc::now().to_rfc3339(),
7782
+ entry_type: "audit".to_string(),
7783
+ };
7784
+ info!("{}", serde_json::to_string(&entry).unwrap_or_default());
7785
+ }
7786
+ `, description: "Create Rust audit logger (tracing)", ruleId: "CONFIG-010" });
7215
7787
  }
7216
7788
  return actions;
7217
7789
  }
@@ -7223,20 +7795,37 @@ function buildSecretsFix(root, f) {
7223
7795
  const idx = (f.line || 1) - 1;
7224
7796
  if (idx >= lines.length) return actions;
7225
7797
  const line = lines[idx];
7798
+ const lang = detectProjectLanguage(root);
7226
7799
  const match = line.match(/(\w+)\s*[:=]\s*['"]([^'"]+)['"]/);
7227
7800
  if (match) {
7228
7801
  const varName = match[1];
7229
7802
  const value = match[2];
7230
- const envFile = fs2.existsSync(path3.join(root, ".env")) ? ".env" : ".env";
7231
- actions.push({ type: "append", filePath: envFile, content: `
7803
+ actions.push({ type: "append", filePath: ".env", content: `
7232
7804
  ${varName}=${value}
7233
7805
  `, description: `Move ${varName} to .env`, ruleId: "SECRETS-001" });
7234
- actions.push({ type: "modify", filePath: f.file, search: line, replace: `${varName}: process.env.${varName}`, description: `Replace hardcoded ${varName}`, ruleId: "SECRETS-001" });
7806
+ let replacement;
7807
+ if (lang === "python") {
7808
+ replacement = line.replace(match[0], `${varName} = os.environ.get('${varName}')`);
7809
+ } else if (lang === "ruby") {
7810
+ replacement = line.replace(match[0], `${varName} = ENV['${varName}']`);
7811
+ } else if (lang === "go") {
7812
+ replacement = line.replace(match[0], `${varName} := os.Getenv("${varName}")`);
7813
+ } else if (lang === "java") {
7814
+ replacement = line.replace(match[0], `String ${varName} = System.getenv("${varName}")`);
7815
+ } else if (lang === "php") {
7816
+ replacement = line.replace(match[0], `$${varName} = getenv('${varName}')`);
7817
+ } else if (lang === "rust") {
7818
+ replacement = line.replace(match[0], `let ${varName} = std::env::var("${varName}").unwrap_or_default()`);
7819
+ } else {
7820
+ replacement = `${varName}: process.env.${varName}`;
7821
+ }
7822
+ actions.push({ type: "modify", filePath: f.file, search: line, replace: replacement, description: `Replace hardcoded ${varName} with env variable`, ruleId: "SECRETS-001" });
7235
7823
  actions.push(...buildEnvGitignoreFix(root));
7236
7824
  }
7237
7825
  return actions;
7238
7826
  }
7239
7827
  function buildWeakHashFix(root, f) {
7828
+ const lang = detectProjectLanguage(root);
7240
7829
  const content = readFileSafe(path3.join(root, f.file));
7241
7830
  if (!content) return [];
7242
7831
  const lines = content.split("\n");
@@ -7244,24 +7833,33 @@ function buildWeakHashFix(root, f) {
7244
7833
  if (idx >= lines.length) return [];
7245
7834
  const line = lines[idx];
7246
7835
  let replacement = line;
7247
- if (/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/.test(line)) {
7836
+ if (lang === "python") {
7837
+ replacement = line.replace(/hashlib\.md5\(/gi, "hashlib.sha256(").replace(/hashlib\.sha1\(/gi, "hashlib.sha256(");
7838
+ } else if (lang === "go") {
7839
+ replacement = line.replace(/md5\.New\(\)/gi, "sha256.New()").replace(/sha1\.New\(\)/gi, "sha256.New()");
7840
+ } else if (lang === "ruby") {
7841
+ replacement = line.replace(/Digest::MD5/gi, "Digest::SHA256").replace(/Digest::SHA1/gi, "Digest::SHA256");
7842
+ } else if (lang === "java") {
7843
+ replacement = line.replace(/MessageDigest\.getInstance\(["']MD5["']\)/gi, 'MessageDigest.getInstance("SHA-256")').replace(/MessageDigest\.getInstance\(["']SHA-1["']\)/gi, 'MessageDigest.getInstance("SHA-256")');
7844
+ } else if (lang === "php") {
7845
+ replacement = line.replace(/md5\(/gi, "hash('sha256', ").replace(/sha1\(/gi, "hash('sha256', ");
7846
+ } else if (lang === "rust") {
7847
+ replacement = line.replace(/md5::compute/gi, "sha2::Sha256::digest").replace(/use md5/gi, "use sha2::{Sha256, Digest}");
7848
+ } else {
7248
7849
  replacement = line.replace(/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/, "createHash('sha256')");
7249
- } else if (/hashlib\.md5\(/i.test(line)) {
7250
- replacement = line.replace(/hashlib\.md5\(/gi, "hashlib.sha256(");
7251
- } else if (/hashlib\.sha1\(/i.test(line)) {
7252
- replacement = line.replace(/hashlib\.sha1\(/gi, "hashlib.sha256(");
7253
7850
  }
7254
7851
  if (replacement === line) return [];
7255
7852
  return [{ type: "modify", filePath: f.file, search: line, replace: replacement, description: "Replace weak hash with SHA-256", ruleId: "CRYPTO-001" }];
7256
7853
  }
7257
7854
  function buildPasswordFix(root, _f) {
7258
- const hasSrc = fs2.existsSync(path3.join(root, "src"));
7259
- const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
7260
- const actions = [
7261
- { type: "npm-install", filePath: "package.json", description: "Install argon2", ruleId: "CRYPTO-003" }
7262
- ];
7263
- if (!fs2.existsSync(path3.join(root, authPath))) {
7264
- actions.push({ type: "create", filePath: authPath, content: `import argon2 from 'argon2';
7855
+ const lang = detectProjectLanguage(root);
7856
+ const actions = [];
7857
+ if (lang === "typescript" || lang === "javascript") {
7858
+ const hasSrc = fs2.existsSync(path3.join(root, "src"));
7859
+ const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
7860
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install argon2", ruleId: "CRYPTO-003" });
7861
+ if (!fs2.existsSync(path3.join(root, authPath))) {
7862
+ actions.push({ type: "create", filePath: authPath, content: `import argon2 from 'argon2';
7265
7863
 
7266
7864
  export async function hashPassword(password: string): Promise<string> {
7267
7865
  return argon2.hash(password, { type: argon2.argon2id });
@@ -7271,18 +7869,134 @@ export async function verifyPassword(hashedPassword: string, inputPassword: stri
7271
7869
  return argon2.verify(hashedPassword, inputPassword);
7272
7870
  }
7273
7871
  `, description: "Create Argon2id password utility", ruleId: "CRYPTO-003" });
7872
+ }
7873
+ } else if (lang === "python") {
7874
+ actions.push({ type: "create", filePath: "lib/auth.py", content: `import hashlib
7875
+ import os
7876
+
7877
+ def hash_password(password: str) -> str:
7878
+ salt = os.urandom(16)
7879
+ key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
7880
+ return salt.hex() + ':' + key.hex()
7881
+
7882
+ def verify_password(stored: str, provided: str) -> bool:
7883
+ salt_hex, key_hex = stored.split(':')
7884
+ salt = bytes.fromhex(salt_hex)
7885
+ new_key = hashlib.pbkdf2_hmac('sha256', provided.encode(), salt, 100000)
7886
+ return new_key.hex() == key_hex
7887
+ `, description: "Create Python password utility (PBKDF2-SHA256)", ruleId: "CRYPTO-003" });
7888
+ } else if (lang === "go") {
7889
+ actions.push({ type: "create", filePath: "lib/auth.go", content: `package lib
7890
+
7891
+ import (
7892
+ "crypto/rand"
7893
+ "crypto/subtle"
7894
+ "encoding/hex"
7895
+ "golang.org/x/crypto/argon2"
7896
+ )
7897
+
7898
+ func HashPassword(password string) (string, error) {
7899
+ salt := make([]byte, 16)
7900
+ if _, err := rand.Read(salt); err != nil {
7901
+ return "", err
7902
+ }
7903
+ hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
7904
+ return hex.EncodeToString(salt) + ":" + hex.EncodeToString(hash), nil
7905
+ }
7906
+
7907
+ func VerifyPassword(stored, provided string) (bool, error) {
7908
+ parts := strings.SplitN(stored, ":", 2)
7909
+ if len(parts) != 2 { return false, nil }
7910
+ salt, _ := hex.DecodeString(parts[0])
7911
+ storedHash, _ := hex.DecodeString(parts[1])
7912
+ providedHash := argon2.IDKey([]byte(provided), salt, 1, 64*1024, 4, 32)
7913
+ return subtle.ConstantTimeCompare(storedHash, providedHash) == 1, nil
7914
+ }
7915
+ `, description: "Create Go Argon2id password utility", ruleId: "CRYPTO-003" });
7916
+ } else if (lang === "ruby") {
7917
+ actions.push({ type: "create", filePath: "lib/auth.rb", content: `require 'bcrypt'
7918
+
7919
+ def hash_password(password)
7920
+ BCrypt::Password.create(password)
7921
+ end
7922
+
7923
+ def verify_password(stored_hash, provided_password)
7924
+ BCrypt::Password.new(stored_hash) == provided_password
7925
+ end
7926
+ `, description: "Create Ruby BCrypt password utility", ruleId: "CRYPTO-003" });
7927
+ } else if (lang === "java") {
7928
+ actions.push({ type: "create", filePath: "src/main/java/com/example/PasswordUtil.java", content: `package com.example;
7929
+
7930
+ import javax.crypto.SecretKeyFactory;
7931
+ import javax.crypto.spec.PBEKeySpec;
7932
+ import java.security.SecureRandom;
7933
+ import java.util.Base64;
7934
+
7935
+ public class PasswordUtil {
7936
+ private static final int ITERATIONS = 100000;
7937
+ private static final int KEY_LENGTH = 256;
7938
+ private static final SecureRandom RANDOM = new SecureRandom();
7939
+
7940
+ public static String hashPassword(String password) throws Exception {
7941
+ byte[] salt = new byte[16];
7942
+ RANDOM.nextBytes(salt);
7943
+ PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
7944
+ byte[] hash = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded();
7945
+ return Base64.getEncoder().encodeToString(salt) + ":" + Base64.getEncoder().encodeToString(hash);
7946
+ }
7947
+
7948
+ public static boolean verifyPassword(String stored, String provided) throws Exception {
7949
+ String[] parts = stored.split(":");
7950
+ byte[] salt = Base64.getDecoder().decode(parts[0]);
7951
+ byte[] storedHash = Base64.getDecoder().decode(parts[1]);
7952
+ PBEKeySpec spec = new PBEKeySpec(provided.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
7953
+ byte[] testHash = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded();
7954
+ return java.util.Arrays.equals(storedHash, testHash);
7955
+ }
7956
+ }
7957
+ `, description: "Create Java PBKDF2 password utility", ruleId: "CRYPTO-003" });
7958
+ } else if (lang === "php") {
7959
+ actions.push({ type: "create", filePath: "lib/auth.php", content: `<?php
7960
+
7961
+ function hash_password(string $password): string {
7962
+ return password_hash($password, PASSWORD_ARGON2ID);
7963
+ }
7964
+
7965
+ function verify_password(string $hash, string $password): bool {
7966
+ return password_verify($password, $hash);
7967
+ }
7968
+ `, description: "Create PHP Argon2id password utility", ruleId: "CRYPTO-003" });
7969
+ } else if (lang === "rust") {
7970
+ actions.push({ type: "create", filePath: "src/auth.rs", content: `use argon2::{Argon2, Algorithm, Version, Params};
7971
+ use argon2::password_hash::{SaltString, PasswordHasher, PasswordVerifier};
7972
+ use rand::rngs::OsRng;
7973
+
7974
+ pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
7975
+ let salt = SaltString::generate(&mut OsRng);
7976
+ let params = Params::new(65536, 3, 4, Some(32))?;
7977
+ let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
7978
+ let hash = argon2.hash_password(password.as_bytes(), &salt)?;
7979
+ Ok(hash.to_string())
7980
+ }
7981
+
7982
+ pub fn verify_password(hash: &str, password: &str) -> Result<bool, argon2::password_hash::Error> {
7983
+ let parsed = argon2::PasswordHash::new(hash)?;
7984
+ Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
7985
+ }
7986
+ `, description: "Create Rust Argon2id password utility", ruleId: "CRYPTO-003" });
7274
7987
  }
7275
7988
  return actions;
7276
7989
  }
7277
7990
  function buildRateLimitFix(root) {
7278
- const appFile = findMainAppFile(root);
7279
- if (!appFile) return [];
7280
- const isExpress = hasDep(root, "express");
7281
- const isFastify = hasDep(root, "fastify");
7282
- if (isExpress) {
7283
- return [
7284
- { type: "npm-install", filePath: "package.json", description: "Install express-rate-limit", ruleId: "AUTH-002" },
7285
- { type: "append", filePath: appFile, content: `
7991
+ const lang = detectProjectLanguage(root);
7992
+ const fw = detectWebFramework(root, lang);
7993
+ const actions = [];
7994
+ if (lang === "typescript" || lang === "javascript") {
7995
+ const appFile = findMainAppFile(root);
7996
+ if (!appFile) return [];
7997
+ if (fw === "express") {
7998
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install express-rate-limit", ruleId: "AUTH-002" });
7999
+ actions.push({ type: "append", filePath: appFile, content: `
7286
8000
  import rateLimit from 'express-rate-limit';
7287
8001
 
7288
8002
  const limiter = rateLimit({
@@ -7292,29 +8006,91 @@ const limiter = rateLimit({
7292
8006
  legacyHeaders: false,
7293
8007
  });
7294
8008
  app.use(limiter);
7295
- `, description: "Add rate limiting (100 req/15min)", ruleId: "AUTH-002" }
7296
- ];
7297
- } else if (isFastify) {
7298
- return [
7299
- { type: "npm-install", filePath: "package.json", description: "Install @fastify/rate-limit", ruleId: "AUTH-002" },
7300
- { type: "append", filePath: appFile, content: `
8009
+ `, description: "Add rate limiting (100 req/15min)", ruleId: "AUTH-002" });
8010
+ } else if (fw === "fastify") {
8011
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install @fastify/rate-limit", ruleId: "AUTH-002" });
8012
+ actions.push({ type: "append", filePath: appFile, content: `
7301
8013
  import rateLimit from '@fastify/rate-limit';
7302
8014
  app.register(rateLimit, { max: 100, timeWindow: '15 minutes' });
7303
- `, description: "Add rate limiting to Fastify", ruleId: "AUTH-002" }
7304
- ];
8015
+ `, description: "Add rate limiting to Fastify", ruleId: "AUTH-002" });
8016
+ }
8017
+ } else if (lang === "python") {
8018
+ const appFile = findMainAppFile(root) || "app.py";
8019
+ if (fw === "django") {
8020
+ 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" });
8021
+ } else if (fw === "fastapi") {
8022
+ 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" });
8023
+ } else if (fw === "flask") {
8024
+ 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" });
8025
+ }
8026
+ } else if (lang === "ruby") {
8027
+ if (fw === "rails") {
8028
+ actions.push({ type: "append", filePath: "Gemfile", content: "\ngem 'rack-attack'\n", description: "Add rack-attack for rate limiting", ruleId: "AUTH-002" });
8029
+ 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" });
8030
+ }
8031
+ } else if (lang === "go") {
8032
+ const appFile = findMainAppFile(root) || "main.go";
8033
+ actions.push({ type: "append", filePath: appFile, content: '\nimport (\n "net/http"\n "sync"\n "time"\n)\n\ntype rateLimiter struct {\n mu sync.Mutex\n visitors map[string][]time.Time\n limit int\n window time.Duration\n}\n\nfunc newRateLimiter(limit int, window time.Duration) *rateLimiter {\n return &rateLimiter{visitors: make(map[string][]time.Time), limit: limit, window: window}\n}\n\nfunc (rl *rateLimiter) allow(ip string) bool {\n rl.mu.Lock()\n defer rl.mu.Unlock()\n now := time.Now()\n windowStart := now.Add(-rl.window)\n var recent []time.Time\n for _, t := range rl.visitors[ip] {\n if t.After(windowStart) { recent = append(recent, t) }\n }\n rl.visitors[ip] = recent\n if len(recent) >= rl.limit { return false }\n rl.visitors[ip] = append(rl.visitors[ip], now)\n return true\n}\n\nvar limiter = newRateLimiter(100, 15*time.Minute)\n\nfunc rateLimitMiddleware(next http.Handler) http.Handler {\n return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n if !limiter.allow(r.RemoteAddr) {\n http.Error(w, "Too many requests", http.StatusTooManyRequests)\n return\n }\n next.ServeHTTP(w, r)\n })\n}\n', description: "Add Go rate limiter middleware", ruleId: "AUTH-002" });
8034
+ } else if (lang === "java") {
8035
+ if (fw === "spring") {
8036
+ actions.push({ type: "create", filePath: "src/main/java/com/example/RateLimitConfig.java", content: `package com.example;
8037
+
8038
+ import io.github.bucket4j.Bandwidth;
8039
+ import io.github.bucket4j.Bucket;
8040
+ import io.github.bucket4j.Refill;
8041
+ import org.springframework.stereotype.Component;
8042
+ import org.springframework.web.servlet.HandlerInterceptor;
8043
+
8044
+ import jakarta.servlet.http.HttpServletRequest;
8045
+ import jakarta.servlet.http.HttpServletResponse;
8046
+ import java.time.Duration;
8047
+ import java.util.Map;
8048
+ import java.util.concurrent.ConcurrentHashMap;
8049
+
8050
+ @Component
8051
+ public class RateLimitInterceptor implements HandlerInterceptor {
8052
+ private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
8053
+
8054
+ private Bucket newBucket() {
8055
+ Bandwidth limit = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(15)));
8056
+ return Bucket.builder().addLimit(limit).build();
8057
+ }
8058
+
8059
+ @Override
8060
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
8061
+ Bucket bucket = buckets.computeIfAbsent(request.getRemoteAddr(), k -> newBucket());
8062
+ if (bucket.tryConsume(1)) return true;
8063
+ response.setStatus(429);
8064
+ return false;
8065
+ }
8066
+ }
8067
+ `, description: "Create Spring rate limiter (bucket4j)", ruleId: "AUTH-002" });
8068
+ }
8069
+ } else if (lang === "php") {
8070
+ const appFile = findMainAppFile(root) || "public/index.php";
8071
+ 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" });
8072
+ } else if (lang === "rust") {
8073
+ const appFile = findMainAppFile(root) || "src/main.rs";
8074
+ if (fw === "actix") {
8075
+ 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" });
8076
+ } else if (fw === "axum") {
8077
+ 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" });
8078
+ } else {
8079
+ 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" });
8080
+ }
7305
8081
  }
7306
- return [];
8082
+ return actions;
7307
8083
  }
7308
8084
  function buildSessionTimeoutFix(root) {
7309
- const appFile = findMainAppFile(root);
7310
- if (!appFile) return [];
7311
- const isExpress = hasDep(root, "express");
7312
- if (!isExpress) return [{ type: "append", filePath: appFile, content: "\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n", description: "Add session timeout constant", ruleId: "AUTH-003" }];
7313
- const content = readFileSafe(path3.join(root, appFile)) || "";
7314
- if (content.includes("session(")) return [];
7315
- return [
7316
- { type: "npm-install", filePath: "package.json", description: "Install express-session", ruleId: "AUTH-003" },
7317
- { type: "append", filePath: appFile, content: `
8085
+ const lang = detectProjectLanguage(root);
8086
+ const fw = detectWebFramework(root, lang);
8087
+ const actions = [];
8088
+ if (lang === "typescript" || lang === "javascript") {
8089
+ const appFile = findMainAppFile(root);
8090
+ if (!appFile) return [];
8091
+ if (fw === "express") {
8092
+ actions.push({ type: "npm-install", filePath: "package.json", description: "Install express-session", ruleId: "AUTH-003" });
8093
+ actions.push({ type: "append", filePath: appFile, content: `
7318
8094
  import session from 'express-session';
7319
8095
 
7320
8096
  app.use(session({
@@ -7323,60 +8099,229 @@ app.use(session({
7323
8099
  saveUninitialized: false,
7324
8100
  cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 30 * 60 * 1000 },
7325
8101
  }));
7326
- `, description: "Add session with 30-min timeout", ruleId: "AUTH-003" }
7327
- ];
8102
+ `, description: "Add session with 30-min timeout", ruleId: "AUTH-003" });
8103
+ } else {
8104
+ actions.push({ type: "append", filePath: appFile, content: "\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n", description: "Add session timeout constant", ruleId: "AUTH-003" });
8105
+ }
8106
+ } else if (lang === "python") {
8107
+ if (fw === "django") {
8108
+ const settingsFile = findFileRecursive(root, "settings.py", ".") || "settings.py";
8109
+ 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" });
8110
+ } else {
8111
+ const appFile = findMainAppFile(root) || "app.py";
8112
+ 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" });
8113
+ }
8114
+ } else if (lang === "ruby") {
8115
+ if (fw === "rails") {
8116
+ 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" });
8117
+ }
8118
+ } else if (lang === "go") {
8119
+ const appFile = findMainAppFile(root) || "main.go";
8120
+ actions.push({ type: "append", filePath: appFile, content: "\nconst sessionTimeout = 30 * time.Minute\n", description: "Add Go session timeout constant", ruleId: "AUTH-003" });
8121
+ } else if (lang === "java") {
8122
+ if (fw === "spring") {
8123
+ 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" });
8124
+ }
8125
+ } else if (lang === "php") {
8126
+ if (fw === "laravel") {
8127
+ 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" });
8128
+ } else {
8129
+ const appFile = findMainAppFile(root) || "public/index.php";
8130
+ 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" });
8131
+ }
8132
+ } else if (lang === "rust") {
8133
+ const appFile = findMainAppFile(root) || "src/main.rs";
8134
+ 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" });
8135
+ }
8136
+ return actions;
7328
8137
  }
7329
8138
  function buildCORSWildcardFix(root) {
8139
+ const lang = detectProjectLanguage(root);
7330
8140
  const appFile = findMainAppFile(root);
7331
8141
  if (!appFile) return [];
7332
8142
  const content = readFileSafe(path3.join(root, appFile)) || "";
7333
8143
  const actions = [];
7334
- if (content.includes("origin: '*'")) {
7335
- 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" });
7336
- }
7337
- if (content.includes('origin:"*"')) {
7338
- 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" });
8144
+ const wildcardPatterns = ["origin: '*'", "origin:'*'", 'origin:"*"', "Access-Control-Allow-Origin: *"];
8145
+ for (const pattern of wildcardPatterns) {
8146
+ if (!content.includes(pattern)) continue;
8147
+ if (lang === "python") {
8148
+ const replacement = pattern.includes("*'") || pattern.includes('*"') ? "origins=['http://localhost:3000']" : "origins=['http://localhost:3000']";
8149
+ actions.push({ type: "modify", filePath: appFile, search: pattern, replace: replacement, description: "Replace CORS wildcard", ruleId: "AUTH-004" });
8150
+ } else if (lang === "go") {
8151
+ 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" });
8152
+ } else if (lang === "ruby") {
8153
+ 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" });
8154
+ } else if (lang === "java") {
8155
+ 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" });
8156
+ } else if (lang === "php") {
8157
+ 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" });
8158
+ } else if (lang === "rust") {
8159
+ 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" });
8160
+ } else {
8161
+ 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" });
8162
+ }
7339
8163
  }
7340
8164
  return actions;
7341
8165
  }
7342
8166
  function buildTimestampsFix(root, f) {
7343
- if (!f.file.endsWith(".prisma")) return [];
7344
- const content = readFileSafe(path3.join(root, f.file));
7345
- if (!content) return [];
7346
- const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
7347
- if (!modelMatch || modelMatch.length === 0) return [];
7348
- const block = modelMatch[0];
7349
- const closingBrace = block.lastIndexOf("}");
7350
- if (closingBrace === -1) return [];
7351
- const insertion = "\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt";
7352
- 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" }];
8167
+ if (f.file.endsWith(".prisma")) {
8168
+ const content = readFileSafe(path3.join(root, f.file));
8169
+ if (!content) return [];
8170
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
8171
+ if (!modelMatch || modelMatch.length === 0) return [];
8172
+ const block = modelMatch[0];
8173
+ const closingBrace = block.lastIndexOf("}");
8174
+ if (closingBrace === -1) return [];
8175
+ const insertion = "\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt";
8176
+ 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" }];
8177
+ }
8178
+ if (f.file.endsWith(".py")) {
8179
+ 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" }];
8180
+ }
8181
+ if (f.file.endsWith(".rb")) {
8182
+ 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" }];
8183
+ }
8184
+ if (f.file.endsWith(".go")) {
8185
+ 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" }];
8186
+ }
8187
+ if (f.file.endsWith(".java")) {
8188
+ 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" }];
8189
+ }
8190
+ if (f.file.endsWith(".php")) {
8191
+ 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" }];
8192
+ }
8193
+ if (f.file.endsWith(".rs")) {
8194
+ 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" }];
8195
+ }
8196
+ return [];
7353
8197
  }
7354
8198
  function buildSoftDeleteFix(root, f) {
7355
- if (!f.file.endsWith(".prisma")) return [];
7356
- const content = readFileSafe(path3.join(root, f.file));
7357
- if (!content) return [];
7358
- const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
7359
- if (!modelMatch || modelMatch.length === 0) return [];
7360
- const block = modelMatch[0];
7361
- const closingBrace = block.lastIndexOf("}");
7362
- if (closingBrace === -1) return [];
7363
- 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" }];
8199
+ if (f.file.endsWith(".prisma")) {
8200
+ const content = readFileSafe(path3.join(root, f.file));
8201
+ if (!content) return [];
8202
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
8203
+ if (!modelMatch || modelMatch.length === 0) return [];
8204
+ const block = modelMatch[0];
8205
+ const closingBrace = block.lastIndexOf("}");
8206
+ if (closingBrace === -1) return [];
8207
+ 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" }];
8208
+ }
8209
+ if (f.file.endsWith(".py")) {
8210
+ 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" }];
8211
+ }
8212
+ if (f.file.endsWith(".go")) {
8213
+ 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" }];
8214
+ }
8215
+ if (f.file.endsWith(".rs")) {
8216
+ 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" }];
8217
+ }
8218
+ return [];
7364
8219
  }
7365
8220
  function buildUserAuditFix(root, f) {
7366
- if (!f.file.endsWith(".prisma")) return [];
7367
- const content = readFileSafe(path3.join(root, f.file));
7368
- if (!content) return [];
7369
- const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
7370
- if (!modelMatch || modelMatch.length === 0) return [];
7371
- const block = modelMatch[0];
7372
- const closingBrace = block.lastIndexOf("}");
7373
- if (closingBrace === -1) return [];
7374
- 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" }];
8221
+ if (f.file.endsWith(".prisma")) {
8222
+ const content = readFileSafe(path3.join(root, f.file));
8223
+ if (!content) return [];
8224
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
8225
+ if (!modelMatch || modelMatch.length === 0) return [];
8226
+ const block = modelMatch[0];
8227
+ const closingBrace = block.lastIndexOf("}");
8228
+ if (closingBrace === -1) return [];
8229
+ 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" }];
8230
+ }
8231
+ if (f.file.endsWith(".py")) {
8232
+ 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" }];
8233
+ }
8234
+ if (f.file.endsWith(".rs")) {
8235
+ 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" }];
8236
+ }
8237
+ return [];
7375
8238
  }
7376
8239
  function buildAuditModelFix(root) {
7377
- const content = readFileSafe(path3.join(root, "prisma/schema.prisma"));
7378
- if (!content) return [];
7379
- 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" }];
8240
+ if (fs2.existsSync(path3.join(root, "prisma/schema.prisma"))) {
8241
+ 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" }];
8242
+ }
8243
+ const lang = detectProjectLanguage(root);
8244
+ if (lang === "python") {
8245
+ return [{ type: "create", filePath: "lib/models/audit.py", content: `from datetime import datetime
8246
+ from sqlalchemy import Column, Integer, String, DateTime, JSON
8247
+ from sqlalchemy.ext.declarative import declarative_base
8248
+
8249
+ Base = declarative_base()
8250
+
8251
+ class Audit(Base):
8252
+ __tablename__ = 'audit'
8253
+ id = Column(Integer, primary_key=True, autoincrement=True)
8254
+ user_id = Column(String(255))
8255
+ action = Column(String(255))
8256
+ resource = Column(String(255))
8257
+ timestamp = Column(DateTime, default=datetime.utcnow)
8258
+ ip_address = Column(String(45))
8259
+ metadata = Column(JSON)
8260
+ `, description: "Create Python Audit model (SQLAlchemy)", ruleId: "DB-004" }];
8261
+ }
8262
+ if (lang === "go") {
8263
+ return [{ type: "create", filePath: "lib/models/audit.go", content: `package models
8264
+
8265
+ import "time"
8266
+
8267
+ type Audit struct {
8268
+ ID uint \`json:"id" gorm:"primaryKey;autoIncrement"\`
8269
+ UserID string \`json:"userId"\`
8270
+ Action string \`json:"action"\`
8271
+ Resource string \`json:"resource"\`
8272
+ Timestamp time.Time \`json:"timestamp" gorm:"default:now()"\`
8273
+ IPAddress string \`json:"ipAddress"\`
8274
+ }
8275
+ `, description: "Create Go Audit model (GORM)", ruleId: "DB-004" }];
8276
+ }
8277
+ if (lang === "java") {
8278
+ return [{ type: "create", filePath: "src/main/java/com/example/Audit.java", content: `package com.example;
8279
+
8280
+ import jakarta.persistence.*;
8281
+ import java.time.Instant;
8282
+
8283
+ @Entity
8284
+ @Table(name = "audit")
8285
+ public class Audit {
8286
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
8287
+ private Long id;
8288
+ private String userId;
8289
+ private String action;
8290
+ private String resource;
8291
+ private String ipAddress;
8292
+ @Column(columnDefinition = "jsonb")
8293
+ private String metadata;
8294
+ private Instant timestamp = Instant.now();
8295
+ }
8296
+ `, description: "Create Java Audit entity (JPA)", ruleId: "DB-004" }];
8297
+ }
8298
+ if (lang === "rust") {
8299
+ return [{ type: "create", filePath: "src/models/audit.rs", content: `use chrono::NaiveDateTime;
8300
+
8301
+ #[derive(Debug, Queryable, Serialize)]
8302
+ pub struct Audit {
8303
+ pub id: i32,
8304
+ pub user_id: String,
8305
+ pub action: String,
8306
+ pub resource: String,
8307
+ pub ip_address: String,
8308
+ pub timestamp: NaiveDateTime,
8309
+ }
8310
+
8311
+ // Diesel table definition:
8312
+ // table! {
8313
+ // audit (id) {
8314
+ // id -> Int4,
8315
+ // user_id -> Varchar,
8316
+ // action -> Varchar,
8317
+ // resource -> Varchar,
8318
+ // ip_address -> Varchar,
8319
+ // timestamp -> Timestamp,
8320
+ // }
8321
+ // }
8322
+ `, description: "Create Rust Audit model (Diesel)", ruleId: "DB-004" }];
8323
+ }
8324
+ return [];
7380
8325
  }
7381
8326
  function getNpmInstallsFromActions(actions) {
7382
8327
  const installs = /* @__PURE__ */ new Set();
@@ -7395,6 +8340,36 @@ function getNpmInstallsFromActions(actions) {
7395
8340
  return [...installs];
7396
8341
  }
7397
8342
  function buildEncryptionAtRestImpl(root, hasSrc) {
8343
+ const lang = detectProjectLanguage(root);
8344
+ if (lang === "rust") {
8345
+ return [
8346
+ { type: "create", filePath: "src/encryption.rs", content: `use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
8347
+ use aes_gcm::aead::Aead;
8348
+ use rand::RngCore;
8349
+ use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
8350
+
8351
+ pub fn encrypt(plaintext: &str, key: &[u8; 32]) -> Result<String, aes_gcm::Error> {
8352
+ let cipher = Aes256Gcm::new(key.into());
8353
+ let mut nonce_bytes = [0u8; 12];
8354
+ rand::thread_rng().fill_bytes(&mut nonce_bytes);
8355
+ let nonce = Nonce::from_slice(&nonce_bytes);
8356
+ let ciphertext = cipher.encrypt(nonce, plaintext.as_bytes())?;
8357
+ let mut combined = nonce_bytes.to_vec();
8358
+ combined.extend_from_slice(&ciphertext);
8359
+ Ok(BASE64.encode(&combined))
8360
+ }
8361
+
8362
+ pub fn decrypt(encoded: &str, key: &[u8; 32]) -> Result<String, aes_gcm::Error> {
8363
+ let combined = BASE64.decode(encoded).map_err(|_| aes_gcm::Error)?;
8364
+ let (nonce_bytes, ciphertext) = combined.split_at(12);
8365
+ let cipher = Aes256Gcm::new(key.into());
8366
+ let nonce = Nonce::from_slice(nonce_bytes);
8367
+ let plaintext = cipher.decrypt(nonce, ciphertext)?;
8368
+ String::from_utf8(plaintext).map_err(|_| aes_gcm::Error)
8369
+ }
8370
+ `, description: "Create Rust AES-256-GCM encryption utility", ruleId: "GDPR-ART32-002" }
8371
+ ];
8372
+ }
7398
8373
  const cryptoPath = hasSrc ? "src/lib/encryption.ts" : "lib/encryption.ts";
7399
8374
  return [
7400
8375
  { type: "npm-install", filePath: "package.json", description: "Node.js crypto is built-in", ruleId: "GDPR-ART32-002" },
@@ -7433,14 +8408,45 @@ export function decrypt(ciphertext: string, secret: string): string {
7433
8408
  ];
7434
8409
  }
7435
8410
  function buildEncryptionInTransitImpl(root, _hasSrc) {
8411
+ const lang = detectProjectLanguage(root);
7436
8412
  const appFile = findMainAppFile(root);
7437
8413
  const actions = [];
8414
+ if (lang === "rust") {
8415
+ if (appFile) {
8416
+ 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" });
8417
+ }
8418
+ return actions;
8419
+ }
7438
8420
  if (appFile) {
7439
8421
  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" });
7440
8422
  }
7441
8423
  return actions;
7442
8424
  }
7443
8425
  function buildUserIdentificationImpl(root, hasSrc) {
8426
+ const lang = detectProjectLanguage(root);
8427
+ if (lang === "rust") {
8428
+ const authPath2 = "src/auth.rs";
8429
+ if (fs2.existsSync(path3.join(root, authPath2))) return [];
8430
+ return [
8431
+ { type: "create", filePath: authPath2, content: `use argon2::{Argon2, Algorithm, Version, Params};
8432
+ use argon2::password_hash::{SaltString, PasswordHasher, PasswordVerifier};
8433
+ use rand::rngs::OsRng;
8434
+
8435
+ pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
8436
+ let salt = SaltString::generate(&mut OsRng);
8437
+ let params = Params::new(65536, 3, 4, Some(32))?;
8438
+ let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
8439
+ let hash = argon2.hash_password(password.as_bytes(), &salt)?;
8440
+ Ok(hash.to_string())
8441
+ }
8442
+
8443
+ pub fn verify_password(hash: &str, password: &str) -> Result<bool, argon2::password_hash::Error> {
8444
+ let parsed = argon2::PasswordHash::new(hash)?;
8445
+ Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
8446
+ }
8447
+ `, description: "Create Rust auth utility with Argon2id", ruleId: "GDPR-ART32-004" }
8448
+ ];
8449
+ }
7444
8450
  const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
7445
8451
  if (fs2.existsSync(path3.join(root, authPath))) return [];
7446
8452
  return [
@@ -7458,6 +8464,29 @@ export async function verifyPassword(hashedPassword: string, inputPassword: stri
7458
8464
  ];
7459
8465
  }
7460
8466
  function buildIntegrityControlsImpl(root, hasSrc) {
8467
+ const lang = detectProjectLanguage(root);
8468
+ if (lang === "rust") {
8469
+ return [
8470
+ { type: "create", filePath: "src/integrity.rs", content: `use sha2::{Sha256, Digest};
8471
+
8472
+ pub fn hash_data(data: &str) -> String {
8473
+ let mut hasher = Sha256::new();
8474
+ hasher.update(data.as_bytes());
8475
+ format!("{:x}", hasher.finalize())
8476
+ }
8477
+
8478
+ pub fn verify_integrity(data: &str, expected_hash: &str) -> bool {
8479
+ hash_data(data) == expected_hash
8480
+ }
8481
+
8482
+ pub fn generate_checksum(content: &[u8]) -> String {
8483
+ let mut hasher = Sha256::new();
8484
+ hasher.update(content);
8485
+ format!("{:x}", hasher.finalize())
8486
+ }
8487
+ `, description: "Create Rust integrity verification utility", ruleId: "GDPR-ART32-007" }
8488
+ ];
8489
+ }
7461
8490
  const integrityPath = hasSrc ? "src/lib/integrity.ts" : "lib/integrity.ts";
7462
8491
  return [
7463
8492
  { type: "create", filePath: integrityPath, content: `import { createHash } from 'node:crypto';
@@ -7505,7 +8534,19 @@ echo "[$(date)] Cleaned up backups older than 30 days."
7505
8534
  ];
7506
8535
  }
7507
8536
  function buildSecurityTestingImpl(root) {
7508
- const ghDir = path3.join(root, ".github/workflows");
8537
+ const lang = detectProjectLanguage(root);
8538
+ const setupSteps = lang === "rust" ? ` - uses: actions-rs/toolchain@v1
8539
+ with:
8540
+ toolchain: stable
8541
+ - run: cargo build
8542
+ - name: cargo audit
8543
+ run: cargo install cargo-audit && cargo audit` : ` - uses: actions/setup-node@v4
8544
+ with:
8545
+ node-version: '22'
8546
+ - run: npm ci
8547
+ - name: npm audit
8548
+ run: npm audit --audit-level=high
8549
+ continue-on-error: true`;
7509
8550
  return [
7510
8551
  { type: "create", filePath: ".github/workflows/security-scan.yml", content: `name: Security Scan
7511
8552
  on:
@@ -7521,16 +8562,49 @@ jobs:
7521
8562
  runs-on: ubuntu-latest
7522
8563
  steps:
7523
8564
  - uses: actions/checkout@v4
7524
- - uses: actions/setup-node@v4
7525
- with:
7526
- node-version: '22'
7527
- - run: npm ci
7528
- - name: npm audit
7529
- run: npm audit --audit-level=high
7530
- continue-on-error: true
8565
+ ${setupSteps}
7531
8566
  - name: Run GESF compliance check
7532
8567
  run: npx @greenarmor/ges audit --ci
7533
- `, description: "Create security scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" }
8568
+ `, description: "Create security scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" },
8569
+ { type: "create", filePath: ".github/workflows/sbom-scan.yml", content: `name: SBOM Generation & Scan
8570
+ on:
8571
+ push:
8572
+ branches: [main, master]
8573
+ pull_request:
8574
+ branches: [main, master]
8575
+ schedule:
8576
+ - cron: '0 6 * * 1'
8577
+
8578
+ jobs:
8579
+ sbom:
8580
+ runs-on: ubuntu-latest
8581
+ steps:
8582
+ - uses: actions/checkout@v4
8583
+
8584
+ - name: Generate SBOM with Syft
8585
+ uses: anchore/sbom-action@v0
8586
+ with:
8587
+ image: ""
8588
+ path: .
8589
+ format: cyclonedx-json
8590
+ output-file: sbom.json
8591
+ fail-build: false
8592
+
8593
+ - name: Scan SBOM for vulnerabilities with Grype
8594
+ uses: anchore/scan-action@v6
8595
+ with:
8596
+ sbom: sbom.json
8597
+ fail-build: true
8598
+ severity-cutoff: high
8599
+
8600
+ - name: Upload SBOM artifacts
8601
+ if: always()
8602
+ uses: actions/upload-artifact@v4
8603
+ with:
8604
+ name: sbom-artifacts
8605
+ path: sbom.json
8606
+ retention-days: 90
8607
+ `, description: "Create SBOM generation and scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" }
7534
8608
  ];
7535
8609
  }
7536
8610
  function generateDataInventory(projectName, projectType) {