@qasshq/qass 0.1.2

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 (103) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +163 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +117 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/config.d.ts +4 -0
  8. package/dist/core/config.d.ts.map +1 -0
  9. package/dist/core/config.js +128 -0
  10. package/dist/core/config.js.map +1 -0
  11. package/dist/core/diff-analyzer.d.ts +3 -0
  12. package/dist/core/diff-analyzer.d.ts.map +1 -0
  13. package/dist/core/diff-analyzer.js +194 -0
  14. package/dist/core/diff-analyzer.js.map +1 -0
  15. package/dist/core/discover.d.ts +3 -0
  16. package/dist/core/discover.d.ts.map +1 -0
  17. package/dist/core/discover.js +51 -0
  18. package/dist/core/discover.js.map +1 -0
  19. package/dist/core/license.d.ts +13 -0
  20. package/dist/core/license.d.ts.map +1 -0
  21. package/dist/core/license.js +132 -0
  22. package/dist/core/license.js.map +1 -0
  23. package/dist/core/report.d.ts +4 -0
  24. package/dist/core/report.d.ts.map +1 -0
  25. package/dist/core/report.js +95 -0
  26. package/dist/core/report.js.map +1 -0
  27. package/dist/core/runner.d.ts +3 -0
  28. package/dist/core/runner.d.ts.map +1 -0
  29. package/dist/core/runner.js +136 -0
  30. package/dist/core/runner.js.map +1 -0
  31. package/dist/core/test-planner.d.ts +3 -0
  32. package/dist/core/test-planner.d.ts.map +1 -0
  33. package/dist/core/test-planner.js +107 -0
  34. package/dist/core/test-planner.js.map +1 -0
  35. package/dist/integrations/cursor-rule.d.ts +2 -0
  36. package/dist/integrations/cursor-rule.d.ts.map +1 -0
  37. package/dist/integrations/cursor-rule.js +46 -0
  38. package/dist/integrations/cursor-rule.js.map +1 -0
  39. package/dist/integrations/mcp-server.d.ts +67 -0
  40. package/dist/integrations/mcp-server.d.ts.map +1 -0
  41. package/dist/integrations/mcp-server.js +61 -0
  42. package/dist/integrations/mcp-server.js.map +1 -0
  43. package/dist/runners/api/api-runner.d.ts +3 -0
  44. package/dist/runners/api/api-runner.d.ts.map +1 -0
  45. package/dist/runners/api/api-runner.js +258 -0
  46. package/dist/runners/api/api-runner.js.map +1 -0
  47. package/dist/runners/api/endpoint-discovery.d.ts +3 -0
  48. package/dist/runners/api/endpoint-discovery.d.ts.map +1 -0
  49. package/dist/runners/api/endpoint-discovery.js +106 -0
  50. package/dist/runners/api/endpoint-discovery.js.map +1 -0
  51. package/dist/runners/e2e/playwright-runner.d.ts +3 -0
  52. package/dist/runners/e2e/playwright-runner.d.ts.map +1 -0
  53. package/dist/runners/e2e/playwright-runner.js +309 -0
  54. package/dist/runners/e2e/playwright-runner.js.map +1 -0
  55. package/dist/runners/security/dynamic-checker.d.ts +3 -0
  56. package/dist/runners/security/dynamic-checker.d.ts.map +1 -0
  57. package/dist/runners/security/dynamic-checker.js +136 -0
  58. package/dist/runners/security/dynamic-checker.js.map +1 -0
  59. package/dist/runners/security/rules/auth-middleware.d.ts +13 -0
  60. package/dist/runners/security/rules/auth-middleware.d.ts.map +1 -0
  61. package/dist/runners/security/rules/auth-middleware.js +94 -0
  62. package/dist/runners/security/rules/auth-middleware.js.map +1 -0
  63. package/dist/runners/security/rules/config-audit.d.ts +14 -0
  64. package/dist/runners/security/rules/config-audit.d.ts.map +1 -0
  65. package/dist/runners/security/rules/config-audit.js +91 -0
  66. package/dist/runners/security/rules/config-audit.js.map +1 -0
  67. package/dist/runners/security/rules/dep-audit.d.ts +7 -0
  68. package/dist/runners/security/rules/dep-audit.d.ts.map +1 -0
  69. package/dist/runners/security/rules/dep-audit.js +82 -0
  70. package/dist/runners/security/rules/dep-audit.js.map +1 -0
  71. package/dist/runners/security/rules/input-sanitization.d.ts +12 -0
  72. package/dist/runners/security/rules/input-sanitization.d.ts.map +1 -0
  73. package/dist/runners/security/rules/input-sanitization.js +64 -0
  74. package/dist/runners/security/rules/input-sanitization.js.map +1 -0
  75. package/dist/runners/security/rules/rate-limit-audit.d.ts +11 -0
  76. package/dist/runners/security/rules/rate-limit-audit.d.ts.map +1 -0
  77. package/dist/runners/security/rules/rate-limit-audit.js +51 -0
  78. package/dist/runners/security/rules/rate-limit-audit.js.map +1 -0
  79. package/dist/runners/security/rules/secrets-scan.d.ts +4 -0
  80. package/dist/runners/security/rules/secrets-scan.d.ts.map +1 -0
  81. package/dist/runners/security/rules/secrets-scan.js +129 -0
  82. package/dist/runners/security/rules/secrets-scan.js.map +1 -0
  83. package/dist/runners/security/rules/xss-vectors.d.ts +13 -0
  84. package/dist/runners/security/rules/xss-vectors.d.ts.map +1 -0
  85. package/dist/runners/security/rules/xss-vectors.js +76 -0
  86. package/dist/runners/security/rules/xss-vectors.js.map +1 -0
  87. package/dist/runners/security/static-analyzer.d.ts +7 -0
  88. package/dist/runners/security/static-analyzer.d.ts.map +1 -0
  89. package/dist/runners/security/static-analyzer.js +87 -0
  90. package/dist/runners/security/static-analyzer.js.map +1 -0
  91. package/dist/runners/unit/unit-runner.d.ts +3 -0
  92. package/dist/runners/unit/unit-runner.d.ts.map +1 -0
  93. package/dist/runners/unit/unit-runner.js +157 -0
  94. package/dist/runners/unit/unit-runner.js.map +1 -0
  95. package/dist/types.d.ts +153 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +2 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/util/glob.d.ts +2 -0
  100. package/dist/util/glob.d.ts.map +1 -0
  101. package/dist/util/glob.js +32 -0
  102. package/dist/util/glob.js.map +1 -0
  103. package/package.json +68 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Audits application configuration for security issues.
3
+ *
4
+ * Checks for:
5
+ * - CORS with localhost origins in non-dev configuration
6
+ * - Helmet CSP disabled
7
+ * - Error handlers leaking err.message in production
8
+ * - trust proxy misconfiguration
9
+ * - Hardcoded admin emails as fallbacks
10
+ */
11
+ export async function runConfigAuditRule(files, _config) {
12
+ const findings = [];
13
+ for (const file of files) {
14
+ const lines = file.content.split("\n");
15
+ for (let i = 0; i < lines.length; i++) {
16
+ const line = lines[i];
17
+ if (/cors\s*\(/.test(line) || /origin\s*:/.test(line)) {
18
+ const alreadyFlaggedCors = findings.some((f) => f.rule === "config-audit" && f.file === file.path &&
19
+ f.description.includes("CORS"));
20
+ if (!alreadyFlaggedCors) {
21
+ const context = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
22
+ if (/localhost|127\.0\.0\.1/.test(context) &&
23
+ /origin/.test(context) &&
24
+ !/process\.env|NODE_ENV|isDev|isLocal/.test(context)) {
25
+ findings.push({
26
+ rule: "config-audit",
27
+ severity: "LOW",
28
+ file: file.path,
29
+ line: i + 1,
30
+ description: "CORS configuration includes localhost origins without environment-gating. In production, any local service on these ports could make credentialed cross-origin requests.",
31
+ fix: "Conditionally include localhost origins based on NODE_ENV: origin: process.env.NODE_ENV === 'production' ? [prodOrigins] : [prodOrigins, 'http://localhost:3000']",
32
+ });
33
+ }
34
+ }
35
+ }
36
+ if (/helmet\s*\(/.test(line)) {
37
+ const context = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
38
+ if (/contentSecurityPolicy\s*:\s*false/.test(context)) {
39
+ findings.push({
40
+ rule: "config-audit",
41
+ severity: "LOW",
42
+ file: file.path,
43
+ line: i + 1,
44
+ description: "Content Security Policy is disabled in Helmet configuration. While acceptable for pure JSON APIs, any HTML served (error pages, redirects) will have no CSP protection.",
45
+ fix: "If this is a JSON-only API, this is acceptable. If any HTML is served, enable CSP with appropriate directives.",
46
+ });
47
+ }
48
+ }
49
+ if (/err\.message|error\.message/.test(line) && /res\.(json|send|status)/.test(line)) {
50
+ const context = lines.slice(Math.max(0, i - 5), Math.min(i + 5, lines.length)).join("\n");
51
+ if (!/isDev|isLocal|development|NODE_ENV/.test(context)) {
52
+ findings.push({
53
+ rule: "config-audit",
54
+ severity: "MEDIUM",
55
+ file: file.path,
56
+ line: i + 1,
57
+ description: "Error message (err.message) is sent in API response without checking the environment. Library/database error messages can leak internal details (table names, query structures, file paths).",
58
+ fix: 'Gate error details: res.json({ error: isDev ? err.message : "Internal server error" })',
59
+ });
60
+ }
61
+ }
62
+ if (/ADMIN_EMAILS|admin.*email/i.test(line) && /\|\||fallback|default|\?\?/.test(line)) {
63
+ if (/@/.test(line)) {
64
+ findings.push({
65
+ rule: "config-audit",
66
+ severity: "LOW",
67
+ file: file.path,
68
+ line: i + 1,
69
+ description: "Admin email has a hardcoded fallback. If the environment variable is not set, this specific email automatically gets admin privileges.",
70
+ fix: "Remove the hardcoded fallback. Fail explicitly if ADMIN_EMAILS is not configured: const adminEmails = process.env.ADMIN_EMAILS ?? ''; if (!adminEmails) throw new Error('ADMIN_EMAILS not configured');",
71
+ });
72
+ }
73
+ }
74
+ if (/trust\s*proxy/.test(line)) {
75
+ const context = lines.slice(i, Math.min(i + 3, lines.length)).join("\n");
76
+ if (/true/.test(context)) {
77
+ findings.push({
78
+ rule: "config-audit",
79
+ severity: "LOW",
80
+ file: file.path,
81
+ line: i + 1,
82
+ description: "trust proxy is set to 'true' which trusts all proxies. This can allow clients to spoof their IP address via X-Forwarded-For header.",
83
+ fix: "Set trust proxy to a specific number (e.g., 1 for a single reverse proxy) or to 'loopback' for localhost-only proxies.",
84
+ });
85
+ }
86
+ }
87
+ }
88
+ }
89
+ return findings;
90
+ }
91
+ //# sourceMappingURL=config-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-audit.js","sourceRoot":"","sources":["../../../../src/runners/security/rules/config-audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { QassConfig, SecurityFinding } from "../../../types.js";
2
+ import type { FileContent } from "../static-analyzer.js";
3
+ /**
4
+ * Runs npm audit to check for known dependency vulnerabilities (CVEs).
5
+ */
6
+ export declare function runDepAuditRule(_files: FileContent[], _config: QassConfig, projectPath: string): Promise<SecurityFinding[]>;
7
+ //# sourceMappingURL=dep-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-audit.d.ts","sourceRoot":"","sources":["../../../../src/runners/security/rules/dep-audit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAY,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD;;GAEG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,UAAU,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CA6D5B"}
@@ -0,0 +1,82 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { access } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ const exec = promisify(execFile);
6
+ /**
7
+ * Runs npm audit to check for known dependency vulnerabilities (CVEs).
8
+ */
9
+ export async function runDepAuditRule(_files, _config, projectPath) {
10
+ const findings = [];
11
+ const lockFile = resolve(projectPath, "package-lock.json");
12
+ try {
13
+ await access(lockFile);
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ try {
19
+ const { stdout } = await exec("npm", ["audit", "--json"], {
20
+ cwd: projectPath,
21
+ timeout: 30000,
22
+ });
23
+ const audit = JSON.parse(stdout);
24
+ if (audit.vulnerabilities) {
25
+ for (const [pkg, vuln] of Object.entries(audit.vulnerabilities)) {
26
+ const severity = mapSeverity(vuln.severity);
27
+ if (!severity)
28
+ continue;
29
+ findings.push({
30
+ rule: "dep-audit",
31
+ severity,
32
+ file: "package.json",
33
+ description: `Dependency '${pkg}' has a known ${vuln.severity} vulnerability: ${vuln.title || vuln.url || "see npm audit"}`,
34
+ fix: vuln.fixAvailable
35
+ ? `Run: npm audit fix (or npm install ${pkg}@latest)`
36
+ : `No automatic fix available. Consider replacing '${pkg}' or pinning to a safe version.`,
37
+ });
38
+ }
39
+ }
40
+ }
41
+ catch (e) {
42
+ if (e && typeof e === "object" && "stdout" in e) {
43
+ try {
44
+ const audit = JSON.parse(e.stdout);
45
+ if (audit.vulnerabilities) {
46
+ for (const [pkg, vuln] of Object.entries(audit.vulnerabilities)) {
47
+ const severity = mapSeverity(vuln.severity);
48
+ if (!severity)
49
+ continue;
50
+ findings.push({
51
+ rule: "dep-audit",
52
+ severity,
53
+ file: "package.json",
54
+ description: `Dependency '${pkg}' has a known ${vuln.severity} vulnerability`,
55
+ fix: vuln.fixAvailable
56
+ ? `Run: npm audit fix`
57
+ : `No automatic fix. Consider replacing '${pkg}'.`,
58
+ });
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ // couldn't parse audit output
64
+ }
65
+ }
66
+ }
67
+ return findings;
68
+ }
69
+ function mapSeverity(npmSeverity) {
70
+ switch (npmSeverity) {
71
+ case "critical":
72
+ case "high":
73
+ return "HIGH";
74
+ case "moderate":
75
+ return "MEDIUM";
76
+ case "low":
77
+ return "LOW";
78
+ default:
79
+ return null;
80
+ }
81
+ }
82
+ //# sourceMappingURL=dep-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-audit.js","sourceRoot":"","sources":["../../../../src/runners/security/rules/dep-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEjC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAqB,EACrB,OAAmB,EACnB,WAAmB;IAEnB,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE;YACxD,GAAG,EAAE,WAAW;YAChB,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAEjC,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAM,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;gBACrE,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC5C,IAAI,CAAC,QAAQ;oBAAE,SAAS;gBAExB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,WAAW;oBACjB,QAAQ;oBACR,IAAI,EAAE,cAAc;oBACpB,WAAW,EAAE,eAAe,GAAG,iBAAiB,IAAI,CAAC,QAAQ,mBAAmB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,IAAI,eAAe,EAAE;oBAC3H,GAAG,EAAE,IAAI,CAAC,YAAY;wBACpB,CAAC,CAAC,sCAAsC,GAAG,UAAU;wBACrD,CAAC,CAAC,mDAAmD,GAAG,iCAAiC;iBAC5F,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAE,CAAS,CAAC,MAAM,CAAC,CAAC;gBAC5C,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;oBAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAM,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;wBACrE,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;wBAC5C,IAAI,CAAC,QAAQ;4BAAE,SAAS;wBAExB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,WAAW;4BACjB,QAAQ;4BACR,IAAI,EAAE,cAAc;4BACpB,WAAW,EAAE,eAAe,GAAG,iBAAiB,IAAI,CAAC,QAAQ,gBAAgB;4BAC7E,GAAG,EAAE,IAAI,CAAC,YAAY;gCACpB,CAAC,CAAC,oBAAoB;gCACtB,CAAC,CAAC,yCAAyC,GAAG,IAAI;yBACrD,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,8BAA8B;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,WAAW,CAAC,WAAmB;IACtC,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,UAAU,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,UAAU;YACb,OAAO,QAAQ,CAAC;QAClB,KAAK,KAAK;YACR,OAAO,KAAK,CAAC;QACf;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { QassConfig, SecurityFinding } from "../../../types.js";
2
+ import type { FileContent } from "../static-analyzer.js";
3
+ /**
4
+ * Detects unsanitized user input interpolated into database queries or filters.
5
+ *
6
+ * Checks for:
7
+ * - req.query or req.params values used in template literals inside .or(), .filter(), .rpc()
8
+ * - String concatenation with query/params into Supabase/SQL methods
9
+ * - req.body values used without validation in database operations
10
+ */
11
+ export declare function runInputSanitizationRule(files: FileContent[], _config: QassConfig): Promise<SecurityFinding[]>;
12
+ //# sourceMappingURL=input-sanitization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-sanitization.d.ts","sourceRoot":"","sources":["../../../../src/runners/security/rules/input-sanitization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CAsE5B"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Detects unsanitized user input interpolated into database queries or filters.
3
+ *
4
+ * Checks for:
5
+ * - req.query or req.params values used in template literals inside .or(), .filter(), .rpc()
6
+ * - String concatenation with query/params into Supabase/SQL methods
7
+ * - req.body values used without validation in database operations
8
+ */
9
+ export async function runInputSanitizationRule(files, _config) {
10
+ const findings = [];
11
+ const codeFiles = files.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f.path));
12
+ for (const file of codeFiles) {
13
+ const lines = file.content.split("\n");
14
+ for (let i = 0; i < lines.length; i++) {
15
+ const line = lines[i];
16
+ const orFilterMatch = line.match(/\.(or|filter)\s*\(\s*`[^`]*\$\{[^}]*(req\.(query|params|body)\.[^}]*)\}/);
17
+ if (orFilterMatch) {
18
+ findings.push({
19
+ rule: "input-sanitization",
20
+ severity: "MEDIUM",
21
+ file: file.path,
22
+ line: i + 1,
23
+ description: `${orFilterMatch[2]} is interpolated into a .${orFilterMatch[1]}() call without sanitization. This enables filter injection attacks.`,
24
+ fix: `Sanitize the input before use: const sanitized = (${orFilterMatch[2]} as string || '').replace(/[^a-zA-Z0-9\\s\\-@.]/g, ''); then use the sanitized value.`,
25
+ });
26
+ }
27
+ const stringConcatMatch = line.match(/\.(or|filter|rpc|sql)\s*\([^)]*(?:\+|,\s*["'`].*?\$\{)\s*(req\.(query|params)\.\w+)/);
28
+ if (stringConcatMatch && !orFilterMatch) {
29
+ findings.push({
30
+ rule: "input-sanitization",
31
+ severity: "MEDIUM",
32
+ file: file.path,
33
+ line: i + 1,
34
+ description: `${stringConcatMatch[2]} is concatenated into a database query without sanitization.`,
35
+ fix: `Sanitize the input before use. Strip special characters and limit length.`,
36
+ });
37
+ }
38
+ const directInterpolation = line.match(/(?:ilike|like|eq|neq|contains)\s*[.(]\s*[`"'].*\$\{.*?(req\.(query|params)\.\w+)/);
39
+ if (directInterpolation && !orFilterMatch) {
40
+ findings.push({
41
+ rule: "input-sanitization",
42
+ severity: "LOW",
43
+ file: file.path,
44
+ line: i + 1,
45
+ description: `${directInterpolation[1]} is interpolated into a query filter. While individual filter methods are safer than .or(), input should still be sanitized.`,
46
+ fix: `Validate and sanitize ${directInterpolation[1]} before use.`,
47
+ });
48
+ }
49
+ const evalMatch = line.match(/eval\s*\(\s*(req\.|user)/);
50
+ if (evalMatch) {
51
+ findings.push({
52
+ rule: "input-sanitization",
53
+ severity: "HIGH",
54
+ file: file.path,
55
+ line: i + 1,
56
+ description: `User-controlled input passed to eval(). This is a remote code execution vulnerability.`,
57
+ fix: `Remove eval() entirely. Parse the input using JSON.parse() or a purpose-built parser.`,
58
+ });
59
+ }
60
+ }
61
+ }
62
+ return findings;
63
+ }
64
+ //# sourceMappingURL=input-sanitization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-sanitization.js","sourceRoot":"","sources":["../../../../src/runners/security/rules/input-sanitization.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,KAAoB,EACpB,OAAmB;IAEnB,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACnC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAClC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAEtB,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAC9B,yEAAyE,CAC1E,CAAC;YACF,IAAI,aAAa,EAAE,CAAC;gBAClB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,oBAAoB;oBAC1B,QAAQ,EAAE,QAAQ;oBAClB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,WAAW,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,4BAA4B,aAAa,CAAC,CAAC,CAAC,sEAAsE;oBAClJ,GAAG,EAAE,qDAAqD,aAAa,CAAC,CAAC,CAAC,uFAAuF;iBAClK,CAAC,CAAC;YACL,CAAC;YAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAClC,qFAAqF,CACtF,CAAC;YACF,IAAI,iBAAiB,IAAI,CAAC,aAAa,EAAE,CAAC;gBACxC,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,oBAAoB;oBAC1B,QAAQ,EAAE,QAAQ;oBAClB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,WAAW,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,8DAA8D;oBAClG,GAAG,EAAE,2EAA2E;iBACjF,CAAC,CAAC;YACL,CAAC;YAED,MAAM,mBAAmB,GAAG,IAAI,CAAC,KAAK,CACpC,kFAAkF,CACnF,CAAC;YACF,IAAI,mBAAmB,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC1C,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,oBAAoB;oBAC1B,QAAQ,EAAE,KAAK;oBACf,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,WAAW,EAAE,GAAG,mBAAmB,CAAC,CAAC,CAAC,8HAA8H;oBACpK,GAAG,EAAE,yBAAyB,mBAAmB,CAAC,CAAC,CAAC,cAAc;iBACnE,CAAC,CAAC;YACL,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YACzD,IAAI,SAAS,EAAE,CAAC;gBACd,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,oBAAoB;oBAC1B,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,WAAW,EAAE,wFAAwF;oBACrG,GAAG,EAAE,uFAAuF;iBAC7F,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { QassConfig, SecurityFinding } from "../../../types.js";
2
+ import type { FileContent } from "../static-analyzer.js";
3
+ /**
4
+ * Detects route groups that lack dedicated rate limiters.
5
+ *
6
+ * Checks for:
7
+ * - app.use() route mounting without rate limiter middleware
8
+ * - Sensitive routes (auth, payment, data mutation) relying only on global limiter
9
+ */
10
+ export declare function runRateLimitAuditRule(files: FileContent[], _config: QassConfig): Promise<SecurityFinding[]>;
11
+ //# sourceMappingURL=rate-limit-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-audit.d.ts","sourceRoot":"","sources":["../../../../src/runners/security/rules/rate-limit-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CA8D5B"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Detects route groups that lack dedicated rate limiters.
3
+ *
4
+ * Checks for:
5
+ * - app.use() route mounting without rate limiter middleware
6
+ * - Sensitive routes (auth, payment, data mutation) relying only on global limiter
7
+ */
8
+ export async function runRateLimitAuditRule(files, _config) {
9
+ const findings = [];
10
+ const appFiles = files.filter((f) => /app\.(ts|js)$/.test(f.path) ||
11
+ /server\.(ts|js)$/.test(f.path) ||
12
+ /index\.(ts|js)$/.test(f.path));
13
+ for (const file of appFiles) {
14
+ const lines = file.content.split("\n");
15
+ const hasGlobalLimiter = /app\.use\s*\(\s*(?:global)?[Ll]imiter/.test(file.content);
16
+ for (let i = 0; i < lines.length; i++) {
17
+ const line = lines[i];
18
+ const routeMount = line.match(/app\.use\s*\(\s*["'`](\/[^"'`]+)["'`]\s*,\s*(.*)\)/);
19
+ if (!routeMount)
20
+ continue;
21
+ const path = routeMount[1];
22
+ const args = routeMount[2];
23
+ const hasRateLimiter = /[Ll]imiter|rateLimit|throttle/i.test(args);
24
+ if (hasRateLimiter)
25
+ continue;
26
+ const isSensitive = /auth|login|signup|register|payment|subscription|checkout|admin|token|password|reset/i.test(path);
27
+ if (isSensitive) {
28
+ findings.push({
29
+ rule: "rate-limit-audit",
30
+ severity: "MEDIUM",
31
+ file: file.path,
32
+ line: i + 1,
33
+ description: `Route group ${path} has no dedicated rate limiter.${hasGlobalLimiter ? " Only the global rate limiter applies." : ""} This is a sensitive route that should have stricter rate limiting.`,
34
+ fix: `Add a dedicated rate limiter: app.use("${path}", rateLimiter({ windowMs: 15*60*1000, max: 10 }), ${args})`,
35
+ });
36
+ }
37
+ else if (!hasGlobalLimiter) {
38
+ findings.push({
39
+ rule: "rate-limit-audit",
40
+ severity: "LOW",
41
+ file: file.path,
42
+ line: i + 1,
43
+ description: `Route group ${path} has no rate limiter and no global rate limiter was detected.`,
44
+ fix: `Add rate limiting either globally or per-route group.`,
45
+ });
46
+ }
47
+ }
48
+ }
49
+ return findings;
50
+ }
51
+ //# sourceMappingURL=rate-limit-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-audit.js","sourceRoot":"","sources":["../../../../src/runners/security/rules/rate-limit-audit.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAAoB,EACpB,OAAmB;IAEnB,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAC3B,CAAC,CAAC,EAAE,EAAE,CACJ,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5B,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/B,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CACjC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEvC,MAAM,gBAAgB,GAAG,uCAAuC,CAAC,IAAI,CACnE,IAAI,CAAC,OAAO,CACb,CAAC;QAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAEtB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAC3B,oDAAoD,CACrD,CAAC;YAEF,IAAI,CAAC,UAAU;gBAAE,SAAS;YAE1B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAE3B,MAAM,cAAc,GAClB,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAE9C,IAAI,cAAc;gBAAE,SAAS;YAE7B,MAAM,WAAW,GACf,sFAAsF,CAAC,IAAI,CACzF,IAAI,CACL,CAAC;YAEJ,IAAI,WAAW,EAAE,CAAC;gBAChB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,kBAAkB;oBACxB,QAAQ,EAAE,QAAQ;oBAClB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,WAAW,EAAE,eAAe,IAAI,kCAAkC,gBAAgB,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC,EAAE,qEAAqE;oBACvM,GAAG,EAAE,0CAA0C,IAAI,sDAAsD,IAAI,GAAG;iBACjH,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,kBAAkB;oBACxB,QAAQ,EAAE,KAAK;oBACf,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,WAAW,EAAE,eAAe,IAAI,+DAA+D;oBAC/F,GAAG,EAAE,uDAAuD;iBAC7D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { QassConfig, SecurityFinding } from "../../../types.js";
2
+ import type { FileContent } from "../static-analyzer.js";
3
+ export declare function runSecretsScanRule(files: FileContent[], config: QassConfig): Promise<SecurityFinding[]>;
4
+ //# sourceMappingURL=secrets-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-scan.d.ts","sourceRoot":"","sources":["../../../../src/runners/security/rules/secrets-scan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAqGzD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,EAAE,EACpB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC,CAuC5B"}
@@ -0,0 +1,129 @@
1
+ const SECRET_PATTERNS = [
2
+ {
3
+ name: "AWS Access Key",
4
+ pattern: /(?:AKIA|ASIA)[A-Z0-9]{16}/,
5
+ severity: "HIGH",
6
+ description: "AWS access key ID found in source code",
7
+ },
8
+ {
9
+ name: "AWS Secret Key",
10
+ pattern: /(?:aws_secret_access_key|secret_key)\s*[=:]\s*["']?[A-Za-z0-9/+=]{40}["']?/i,
11
+ severity: "HIGH",
12
+ description: "AWS secret access key found in source code",
13
+ },
14
+ {
15
+ name: "Stripe Secret Key",
16
+ pattern: /sk_live_[A-Za-z0-9]{24,}/,
17
+ severity: "HIGH",
18
+ description: "Stripe live secret key found in source code",
19
+ },
20
+ {
21
+ name: "Stripe Publishable Key (live)",
22
+ pattern: /pk_live_[A-Za-z0-9]{24,}/,
23
+ severity: "MEDIUM",
24
+ description: "Stripe live publishable key in source code (consider using env vars)",
25
+ },
26
+ {
27
+ name: "Private Key",
28
+ pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
29
+ severity: "HIGH",
30
+ description: "Private key found in source code",
31
+ },
32
+ {
33
+ name: "JWT Token",
34
+ pattern: /eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/,
35
+ severity: "HIGH",
36
+ description: "JWT token found hardcoded in source code",
37
+ },
38
+ {
39
+ name: "Generic API Key Assignment",
40
+ pattern: /(?:api[_-]?key|apiKey|api_secret)\s*[=:]\s*["'][A-Za-z0-9_\-]{20,}["']/i,
41
+ severity: "MEDIUM",
42
+ description: "API key appears to be hardcoded instead of using environment variables",
43
+ },
44
+ {
45
+ name: "Generic Secret Assignment",
46
+ pattern: /(?:secret|SECRET|token|TOKEN)\s*[=:]\s*["'][A-Za-z0-9_\-/+=]{16,}["']/,
47
+ severity: "MEDIUM",
48
+ description: "Secret or token appears to be hardcoded",
49
+ },
50
+ {
51
+ name: "Hardcoded Password",
52
+ pattern: /(?:password|passwd|pwd)\s*[=:]\s*["'][^"'\s]{8,}["']/i,
53
+ severity: "MEDIUM",
54
+ description: "Password appears to be hardcoded in source code",
55
+ },
56
+ {
57
+ name: "Database Connection String",
58
+ pattern: /(?:postgres|mysql|mongodb|redis):\/\/[^"'\s]+:[^"'\s]+@[^"'\s]+/,
59
+ severity: "HIGH",
60
+ description: "Database connection string with credentials found in source code",
61
+ },
62
+ {
63
+ name: "SendGrid API Key",
64
+ pattern: /SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/,
65
+ severity: "HIGH",
66
+ description: "SendGrid API key found in source code",
67
+ },
68
+ {
69
+ name: "Slack Webhook",
70
+ pattern: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/,
71
+ severity: "MEDIUM",
72
+ description: "Slack webhook URL found in source code",
73
+ },
74
+ {
75
+ name: "Google API Key",
76
+ pattern: /AIza[A-Za-z0-9_-]{35}/,
77
+ severity: "MEDIUM",
78
+ description: "Google API key found in source code. Consider restricting and using env vars.",
79
+ },
80
+ ];
81
+ const SAFE_FILES = [
82
+ /\.env\.example$/,
83
+ /\.env\.sample$/,
84
+ /config\.example\./,
85
+ /README\.md$/,
86
+ /CHANGELOG/,
87
+ /\.test\./,
88
+ /\.spec\./,
89
+ /\.md$/,
90
+ ];
91
+ export async function runSecretsScanRule(files, config) {
92
+ const findings = [];
93
+ const allowlist = config.security?.secrets_allowlist ?? [];
94
+ for (const file of files) {
95
+ if (SAFE_FILES.some((pattern) => pattern.test(file.path)))
96
+ continue;
97
+ if (file.path.includes("node_modules"))
98
+ continue;
99
+ const lines = file.content.split("\n");
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = lines[i];
102
+ if (/^\s*(\/\/|\/\*|\*|#)/.test(line))
103
+ continue;
104
+ for (const sp of SECRET_PATTERNS) {
105
+ const match = sp.pattern.exec(line);
106
+ if (!match)
107
+ continue;
108
+ const matchedValue = match[0];
109
+ if (allowlist.some((a) => matchedValue.includes(a)))
110
+ continue;
111
+ if (/process\.env|import\.meta\.env|\$\{/.test(line))
112
+ continue;
113
+ if (/example|placeholder|your[_-]|xxx|todo/i.test(matchedValue))
114
+ continue;
115
+ findings.push({
116
+ rule: "secrets-scan",
117
+ severity: sp.severity,
118
+ file: file.path,
119
+ line: i + 1,
120
+ description: `${sp.name}: ${sp.description}`,
121
+ fix: `Move this value to an environment variable. Replace the hardcoded value with process.env.YOUR_VAR_NAME and add it to .env (which should be in .gitignore).`,
122
+ });
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ return findings;
128
+ }
129
+ //# sourceMappingURL=secrets-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-scan.js","sourceRoot":"","sources":["../../../../src/runners/security/rules/secrets-scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { QassConfig, SecurityFinding } from "../../../types.js";
2
+ import type { FileContent } from "../static-analyzer.js";
3
+ /**
4
+ * Detects XSS vectors in frontend and backend code.
5
+ *
6
+ * Checks for:
7
+ * - dangerouslySetInnerHTML with non-static content
8
+ * - SVG file upload acceptance without sanitization
9
+ * - Sanitize middleware that re-decodes HTML entities after stripping tags
10
+ * - innerHTML assignments with user-controlled content
11
+ */
12
+ export declare function runXssVectorsRule(files: FileContent[], _config: QassConfig): Promise<SecurityFinding[]>;
13
+ //# sourceMappingURL=xss-vectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xss-vectors.d.ts","sourceRoot":"","sources":["../../../../src/runners/security/rules/xss-vectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CAgF5B"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Detects XSS vectors in frontend and backend code.
3
+ *
4
+ * Checks for:
5
+ * - dangerouslySetInnerHTML with non-static content
6
+ * - SVG file upload acceptance without sanitization
7
+ * - Sanitize middleware that re-decodes HTML entities after stripping tags
8
+ * - innerHTML assignments with user-controlled content
9
+ */
10
+ export async function runXssVectorsRule(files, _config) {
11
+ const findings = [];
12
+ for (const file of files) {
13
+ const lines = file.content.split("\n");
14
+ for (let i = 0; i < lines.length; i++) {
15
+ const line = lines[i];
16
+ if (/dangerouslySetInnerHTML/.test(line)) {
17
+ const context = lines.slice(Math.max(0, i - 3), i + 4).join("\n");
18
+ const isStatic = /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*["'`]/.test(context);
19
+ if (!isStatic) {
20
+ findings.push({
21
+ rule: "xss-vectors",
22
+ severity: "MEDIUM",
23
+ file: file.path,
24
+ line: i + 1,
25
+ description: "dangerouslySetInnerHTML used with dynamic content. If the content comes from user input, API responses, or a CMS, this is a direct XSS vector.",
26
+ fix: "Use a sanitization library like DOMPurify: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Or better, avoid dangerouslySetInnerHTML entirely and render content as text.",
27
+ });
28
+ }
29
+ }
30
+ if (/\.innerHTML\s*=/.test(line) && !/\.innerHTML\s*=\s*["'`]/.test(line)) {
31
+ findings.push({
32
+ rule: "xss-vectors",
33
+ severity: "MEDIUM",
34
+ file: file.path,
35
+ line: i + 1,
36
+ description: "innerHTML set with a dynamic value. This is an XSS vector if the value contains user-controlled content.",
37
+ fix: "Use textContent instead of innerHTML, or sanitize with DOMPurify before assignment.",
38
+ });
39
+ }
40
+ if (/image\/svg\+xml|\.svg/.test(line) && /accept|ACCEPTED|allowed/i.test(line)) {
41
+ const context = lines.slice(Math.max(0, i - 2), i + 3).join("\n");
42
+ if (/upload|file|input/i.test(context)) {
43
+ findings.push({
44
+ rule: "xss-vectors",
45
+ severity: "MEDIUM",
46
+ file: file.path,
47
+ line: i + 1,
48
+ description: "SVG file uploads are accepted. SVG files can contain embedded JavaScript (<script> tags, onload handlers) that execute when served inline. This is a stored XSS vector.",
49
+ fix: "Either block SVG uploads entirely, or serve uploaded SVGs with Content-Disposition: attachment header. Alternatively, sanitize SVGs server-side by stripping <script> tags and event handlers.",
50
+ });
51
+ }
52
+ }
53
+ if (/&lt;|&gt;|&amp;|&quot;|&#x27;/.test(line) && /\.replace\(/.test(line)) {
54
+ const context = lines.slice(Math.max(0, i - 5), i + 5).join("\n");
55
+ const isSanitizer = /sanitize|strip|clean/i.test(context);
56
+ const stripsHtml = /replace.*<[^>]*>/.test(context);
57
+ if (isSanitizer && stripsHtml) {
58
+ const alreadyFlagged = findings.some((f) => f.rule === "xss-vectors" && f.file === file.path &&
59
+ f.description.includes("re-decodes HTML entities"));
60
+ if (!alreadyFlagged) {
61
+ findings.push({
62
+ rule: "xss-vectors",
63
+ severity: "LOW",
64
+ file: file.path,
65
+ line: i + 1,
66
+ description: "Sanitizer re-decodes HTML entities (&lt; → <, &gt; → >, etc.) after stripping tags. Input like '&lt;script&gt;alert(1)&lt;/script&gt;' becomes '<script>alert(1)</script>' after decoding.",
67
+ fix: "Strip HTML tags WITHOUT re-decoding entities, or use a proper sanitization library like sanitize-html or DOMPurify.",
68
+ });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ return findings;
75
+ }
76
+ //# sourceMappingURL=xss-vectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xss-vectors.js","sourceRoot":"","sources":["../../../../src/runners/security/rules/xss-vectors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { QassConfig, DiffAnalysis, SecurityFinding } from "../../types.js";
2
+ export interface FileContent {
3
+ path: string;
4
+ content: string;
5
+ }
6
+ export declare function runStaticAnalysis(config: QassConfig, projectPath: string, diff?: DiffAnalysis): Promise<SecurityFinding[]>;
7
+ //# sourceMappingURL=static-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-analyzer.d.ts","sourceRoot":"","sources":["../../../src/runners/security/static-analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAY,MAAM,gBAAgB,CAAC;AAe1F,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAYD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CA4C5B"}