@quantakrypto/core 0.3.0 → 0.4.0

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 (166) hide show
  1. package/dist/agent-types.d.ts +48 -0
  2. package/dist/agent-types.d.ts.map +1 -0
  3. package/dist/agent-types.js +2 -0
  4. package/dist/agent-types.js.map +1 -0
  5. package/dist/cache.d.ts +27 -0
  6. package/dist/cache.d.ts.map +1 -0
  7. package/dist/cache.js +93 -0
  8. package/dist/cache.js.map +1 -0
  9. package/dist/cbom.d.ts.map +1 -1
  10. package/dist/cbom.js +26 -1
  11. package/dist/cbom.js.map +1 -1
  12. package/dist/changed.d.ts.map +1 -1
  13. package/dist/changed.js +14 -2
  14. package/dist/changed.js.map +1 -1
  15. package/dist/codemods/config-toggle.d.ts +3 -0
  16. package/dist/codemods/config-toggle.d.ts.map +1 -0
  17. package/dist/codemods/config-toggle.js +26 -0
  18. package/dist/codemods/config-toggle.js.map +1 -0
  19. package/dist/codemods/registry.d.ts +26 -0
  20. package/dist/codemods/registry.d.ts.map +1 -0
  21. package/dist/codemods/registry.js +8 -0
  22. package/dist/codemods/registry.js.map +1 -0
  23. package/dist/comments.d.ts +45 -0
  24. package/dist/comments.d.ts.map +1 -0
  25. package/dist/comments.js +164 -0
  26. package/dist/comments.js.map +1 -0
  27. package/dist/config.d.ts +1 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +7 -0
  30. package/dist/config.js.map +1 -1
  31. package/dist/dependencies.d.ts +31 -13
  32. package/dist/dependencies.d.ts.map +1 -1
  33. package/dist/dependencies.js +460 -40
  34. package/dist/dependencies.js.map +1 -1
  35. package/dist/detect-utils.d.ts +44 -1
  36. package/dist/detect-utils.d.ts.map +1 -1
  37. package/dist/detect-utils.js +113 -17
  38. package/dist/detect-utils.js.map +1 -1
  39. package/dist/detectors/c.d.ts +20 -0
  40. package/dist/detectors/c.d.ts.map +1 -0
  41. package/dist/detectors/c.js +104 -0
  42. package/dist/detectors/c.js.map +1 -0
  43. package/dist/detectors/csharp.d.ts +15 -0
  44. package/dist/detectors/csharp.d.ts.map +1 -0
  45. package/dist/detectors/csharp.js +76 -0
  46. package/dist/detectors/csharp.js.map +1 -0
  47. package/dist/detectors/go.d.ts +25 -0
  48. package/dist/detectors/go.d.ts.map +1 -0
  49. package/dist/detectors/go.js +132 -0
  50. package/dist/detectors/go.js.map +1 -0
  51. package/dist/detectors/java.d.ts +25 -0
  52. package/dist/detectors/java.d.ts.map +1 -0
  53. package/dist/detectors/java.js +200 -0
  54. package/dist/detectors/java.js.map +1 -0
  55. package/dist/detectors/python.d.ts +26 -0
  56. package/dist/detectors/python.d.ts.map +1 -0
  57. package/dist/detectors/python.js +171 -0
  58. package/dist/detectors/python.js.map +1 -0
  59. package/dist/detectors/ruby.d.ts +15 -0
  60. package/dist/detectors/ruby.d.ts.map +1 -0
  61. package/dist/detectors/ruby.js +75 -0
  62. package/dist/detectors/ruby.js.map +1 -0
  63. package/dist/detectors/rust.d.ts +13 -0
  64. package/dist/detectors/rust.d.ts.map +1 -0
  65. package/dist/detectors/rust.js +94 -0
  66. package/dist/detectors/rust.js.map +1 -0
  67. package/dist/detectors/source.d.ts.map +1 -1
  68. package/dist/detectors/source.js +70 -22
  69. package/dist/detectors/source.js.map +1 -1
  70. package/dist/index.d.ts +18 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +15 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/parallel.d.ts +4 -0
  75. package/dist/parallel.d.ts.map +1 -1
  76. package/dist/parallel.js +59 -8
  77. package/dist/parallel.js.map +1 -1
  78. package/dist/patch-policy.d.ts +22 -0
  79. package/dist/patch-policy.d.ts.map +1 -0
  80. package/dist/patch-policy.js +21 -0
  81. package/dist/patch-policy.js.map +1 -0
  82. package/dist/redact.d.ts +19 -0
  83. package/dist/redact.d.ts.map +1 -0
  84. package/dist/redact.js +77 -0
  85. package/dist/redact.js.map +1 -0
  86. package/dist/registry.d.ts +13 -3
  87. package/dist/registry.d.ts.map +1 -1
  88. package/dist/registry.js +31 -4
  89. package/dist/registry.js.map +1 -1
  90. package/dist/remediate-pipeline.d.ts +41 -0
  91. package/dist/remediate-pipeline.d.ts.map +1 -0
  92. package/dist/remediate-pipeline.js +38 -0
  93. package/dist/remediate-pipeline.js.map +1 -0
  94. package/dist/remediate-request.d.ts +21 -0
  95. package/dist/remediate-request.d.ts.map +1 -0
  96. package/dist/remediate-request.js +28 -0
  97. package/dist/remediate-request.js.map +1 -0
  98. package/dist/remediation.d.ts +10 -0
  99. package/dist/remediation.d.ts.map +1 -1
  100. package/dist/remediation.js +21 -8
  101. package/dist/remediation.js.map +1 -1
  102. package/dist/report.d.ts.map +1 -1
  103. package/dist/report.js +16 -1
  104. package/dist/report.js.map +1 -1
  105. package/dist/scan-worker.js +6 -2
  106. package/dist/scan-worker.js.map +1 -1
  107. package/dist/scan.d.ts +5 -4
  108. package/dist/scan.d.ts.map +1 -1
  109. package/dist/scan.js +52 -29
  110. package/dist/scan.js.map +1 -1
  111. package/dist/triage.d.ts +27 -0
  112. package/dist/triage.d.ts.map +1 -0
  113. package/dist/triage.js +33 -0
  114. package/dist/triage.js.map +1 -0
  115. package/dist/types.d.ts +53 -3
  116. package/dist/types.d.ts.map +1 -1
  117. package/dist/verify.d.ts +30 -0
  118. package/dist/verify.d.ts.map +1 -0
  119. package/dist/verify.js +65 -0
  120. package/dist/verify.js.map +1 -0
  121. package/dist/version.d.ts +1 -1
  122. package/dist/version.js +1 -1
  123. package/dist/walk.d.ts +7 -0
  124. package/dist/walk.d.ts.map +1 -1
  125. package/dist/walk.js +85 -11
  126. package/dist/walk.js.map +1 -1
  127. package/dist/worktree.d.ts +6 -0
  128. package/dist/worktree.d.ts.map +1 -0
  129. package/dist/worktree.js +43 -0
  130. package/dist/worktree.js.map +1 -0
  131. package/package.json +1 -1
  132. package/src/agent-types.ts +52 -0
  133. package/src/cache.ts +120 -0
  134. package/src/cbom.ts +27 -1
  135. package/src/changed.ts +13 -4
  136. package/src/codemods/config-toggle.ts +35 -0
  137. package/src/codemods/registry.ts +31 -0
  138. package/src/comments.ts +180 -0
  139. package/src/config.ts +8 -0
  140. package/src/dependencies.ts +483 -51
  141. package/src/detect-utils.ts +123 -15
  142. package/src/detectors/c.ts +132 -0
  143. package/src/detectors/csharp.ts +98 -0
  144. package/src/detectors/go.ts +167 -0
  145. package/src/detectors/java.ts +235 -0
  146. package/src/detectors/python.ts +212 -0
  147. package/src/detectors/ruby.ts +97 -0
  148. package/src/detectors/rust.ts +112 -0
  149. package/src/detectors/source.ts +69 -21
  150. package/src/index.ts +45 -1
  151. package/src/parallel.ts +63 -6
  152. package/src/patch-policy.ts +42 -0
  153. package/src/redact.ts +92 -0
  154. package/src/registry.ts +32 -4
  155. package/src/remediate-pipeline.ts +81 -0
  156. package/src/remediate-request.ts +52 -0
  157. package/src/remediation.ts +27 -11
  158. package/src/report.ts +27 -1
  159. package/src/scan-worker.ts +6 -2
  160. package/src/scan.ts +62 -31
  161. package/src/triage.ts +58 -0
  162. package/src/types.ts +66 -3
  163. package/src/verify.ts +82 -0
  164. package/src/version.ts +1 -1
  165. package/src/walk.ts +81 -12
  166. package/src/worktree.ts +45 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Codemod registry — deterministic, template-able fixes keyed by finding.
3
+ *
4
+ * A codemod only earns a place here if its output reliably CLEARS the finding
5
+ * (the remediation pipeline enforces this with the `verify_fix` gate). That
6
+ * makes the set deliberately small: mechanical config toggles qualify;
7
+ * dependency swaps and source crypto rewrites generally do NOT (there is no
8
+ * safe drop-in replacement, and a manifest fix can't "remove" the finding
9
+ * without deleting the dependency) — those are left to triage + the LLM
10
+ * remediation layer, not an auto-codemod.
11
+ */
12
+ import type { Finding } from "../types.js";
13
+ import type { Patch } from "../agent-types.js";
14
+ import { configToggleCodemod } from "./config-toggle.js";
15
+
16
+ /** A deterministic fix for a class of findings. */
17
+ export interface Codemod {
18
+ id: string;
19
+ /** True when this codemod can produce a deterministic fix for the finding. */
20
+ applies(finding: Finding): boolean;
21
+ /** Produce a patch (full new file content), or null if it changed nothing. */
22
+ apply(content: string, finding: Finding): Patch | null;
23
+ }
24
+
25
+ /** All registered codemods, in priority order. */
26
+ export const codemodRegistry: Codemod[] = [configToggleCodemod];
27
+
28
+ /** The first codemod that applies to `finding`, or undefined. */
29
+ export function codemodFor(finding: Finding): Codemod | undefined {
30
+ return codemodRegistry.find((c) => c.applies(finding));
31
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Comment-aware false-positive suppression.
3
+ *
4
+ * The detectors are lexical (regex over source text), so a crypto API name
5
+ * written in a COMMENT — `// migrated off createECDH()` — fires a finding just
6
+ * like real code. This module computes the comment spans of a file (respecting
7
+ * string literals, so a `//` inside a string is not a comment) and drops any
8
+ * finding whose match starts inside one.
9
+ *
10
+ * The lexer is deliberately conservative: it only ever ENTERS a comment on a
11
+ * literal line comment, block comment, or `#` in code position (all of which ARE
12
+ * comments), so it never mis-classifies code as a comment and can only reduce
13
+ * false positives, not recall. The one inherent lexical ambiguity — a regex
14
+ * literal that looks like a block-comment open — is rare and guarded by the
15
+ * detection benchmark's recall gate.
16
+ */
17
+ import type { Finding } from "./types.js";
18
+
19
+ export type CommentStyle = "c" | "hash";
20
+
21
+ /** C-style (line + block) comment languages, by extension. */
22
+ const C_LIKE: readonly string[] = [
23
+ ".js",
24
+ ".jsx",
25
+ ".ts",
26
+ ".tsx",
27
+ ".mjs",
28
+ ".cjs",
29
+ ".vue",
30
+ ".svelte",
31
+ ".go",
32
+ ".java",
33
+ ".kt",
34
+ ".kts",
35
+ ".cs",
36
+ ".rs",
37
+ ".c",
38
+ ".h",
39
+ ".cc",
40
+ ".cpp",
41
+ ".cxx",
42
+ ".hpp",
43
+ ".hh",
44
+ ];
45
+
46
+ /** Hash-style (`#`) comment languages, by extension. */
47
+ const HASH_LIKE: readonly string[] = [".py", ".pyi", ".pyw", ".rb"];
48
+
49
+ /** The comment style for a file path, or null when we don't strip comments for it. */
50
+ export function commentStyleForFile(file: string): CommentStyle | null {
51
+ const lower = file.toLowerCase();
52
+ if (C_LIKE.some((e) => lower.endsWith(e))) return "c";
53
+ if (HASH_LIKE.some((e) => lower.endsWith(e))) return "hash";
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Compute the comment spans (`[start, end)` offsets) of `content`, skipping over
59
+ * string literals so a comment marker inside a string is not treated as a
60
+ * comment. Spans are returned sorted and non-overlapping.
61
+ */
62
+ export function commentSpans(content: string, style: CommentStyle): Array<[number, number]> {
63
+ const spans: Array<[number, number]> = [];
64
+ const n = content.length;
65
+ let i = 0;
66
+ while (i < n) {
67
+ const c = content[i];
68
+ // String / template literal: skip to the matching unescaped delimiter.
69
+ if (c === '"' || c === "'" || c === "`") {
70
+ const quote = c;
71
+ i++;
72
+ while (i < n) {
73
+ if (content[i] === "\\") {
74
+ i += 2;
75
+ continue;
76
+ }
77
+ if (content[i] === quote) {
78
+ i++;
79
+ break;
80
+ }
81
+ i++;
82
+ }
83
+ continue;
84
+ }
85
+ if (style === "c" && c === "/" && content[i + 1] === "/") {
86
+ const start = i;
87
+ i += 2;
88
+ while (i < n && content[i] !== "\n") i++;
89
+ spans.push([start, i]);
90
+ continue;
91
+ }
92
+ if (style === "c" && c === "/" && content[i + 1] === "*") {
93
+ const start = i;
94
+ i += 2;
95
+ while (i < n && !(content[i] === "*" && content[i + 1] === "/")) i++;
96
+ i = Math.min(n, i + 2);
97
+ spans.push([start, i]);
98
+ continue;
99
+ }
100
+ if (style === "hash" && c === "#") {
101
+ const start = i;
102
+ i++;
103
+ while (i < n && content[i] !== "\n") i++;
104
+ spans.push([start, i]);
105
+ continue;
106
+ }
107
+ i++;
108
+ }
109
+ return spans;
110
+ }
111
+
112
+ /** True if `offset` falls inside one of the (sorted, non-overlapping) spans. */
113
+ export function offsetInSpans(spans: ReadonlyArray<[number, number]>, offset: number): boolean {
114
+ let lo = 0;
115
+ let hi = spans.length - 1;
116
+ while (lo <= hi) {
117
+ const mid = (lo + hi) >>> 1;
118
+ const [s, e] = spans[mid];
119
+ if (offset < s) hi = mid - 1;
120
+ else if (offset >= e) lo = mid + 1;
121
+ else return true;
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Line numbers (1-based) suppressed by an inline ignore directive:
128
+ * - `qscan-ignore-line` on a line suppresses findings on THAT line.
129
+ * - `qscan-ignore-next-line` on a line suppresses findings on the NEXT line.
130
+ * The directive text is matched anywhere on the line (usually in a comment), so
131
+ * it is language-agnostic. `qscan-ignore-line` is not a substring of
132
+ * `qscan-ignore-next-line`, so the two never collide.
133
+ */
134
+ export function ignoredLines(content: string): Set<number> {
135
+ const ignored = new Set<number>();
136
+ const lines = content.split("\n");
137
+ for (let i = 0; i < lines.length; i++) {
138
+ const line = lines[i];
139
+ if (line.includes("qscan-ignore-next-line")) ignored.add(i + 2);
140
+ else if (line.includes("qscan-ignore-line")) ignored.add(i + 1);
141
+ }
142
+ return ignored;
143
+ }
144
+
145
+ /** Drop findings on lines suppressed by an inline `qscan-ignore` directive. */
146
+ export function stripIgnoredFindings(findings: Finding[], content: string): Finding[] {
147
+ if (findings.length === 0 || !content.includes("qscan-ignore")) return findings;
148
+ const ignored = ignoredLines(content);
149
+ if (ignored.size === 0) return findings;
150
+ return findings.filter((f) => !ignored.has(f.location.line));
151
+ }
152
+
153
+ /**
154
+ * Drop findings whose match starts inside a comment. No-op when the file's
155
+ * language has no comment style we handle, or when it has no comments.
156
+ */
157
+ export function stripCommentFindings(
158
+ findings: Finding[],
159
+ content: string,
160
+ file: string,
161
+ ): Finding[] {
162
+ if (findings.length === 0) return findings;
163
+ const style = commentStyleForFile(file);
164
+ if (!style) return findings;
165
+ const spans = commentSpans(content, style);
166
+ if (spans.length === 0) return findings;
167
+
168
+ // 1-based line → start offset, to turn a finding's (line, column) back into an
169
+ // absolute offset (column is 1-based: offset = lineStart + column - 1).
170
+ const lineStarts: number[] = [0];
171
+ for (let i = 0; i < content.length; i++) {
172
+ if (content[i] === "\n") lineStarts.push(i + 1);
173
+ }
174
+
175
+ return findings.filter((f) => {
176
+ const start = lineStarts[f.location.line - 1] ?? 0;
177
+ const offset = start + ((f.location.column ?? 1) - 1);
178
+ return !offsetInSpans(spans, offset);
179
+ });
180
+ }
package/src/config.ts CHANGED
@@ -54,6 +54,7 @@ export interface QuantakryptoFileConfig extends Partial<
54
54
  | "source"
55
55
  | "dependencies"
56
56
  | "config"
57
+ | "disabledRules"
57
58
  >
58
59
  > {
59
60
  /** Gate severity (drives the exit code). Maps to qScan's `severityThreshold`. */
@@ -144,6 +145,7 @@ function mapConfig(
144
145
  "maxFileSize",
145
146
  "scanMinified",
146
147
  "detectors",
148
+ "disabledRules",
147
149
  "languages",
148
150
  "severityThreshold",
149
151
  "baseline",
@@ -216,6 +218,12 @@ function mapConfig(
216
218
  }
217
219
  }
218
220
 
221
+ // disabledRules: rule ids to suppress, threaded to ScanOptions.disabledRules.
222
+ // Unknown ids are harmless (they never match), so we accept the list as-is —
223
+ // this also lets a config forward-reference a rule added in a later version.
224
+ const disabledRules = asStringArray(raw, "disabledRules", file);
225
+ if (disabledRules && disabledRules.length > 0) out.disabledRules = disabledRules;
226
+
219
227
  // languages: forward-looking (inert until the plugin registry lands). Validate
220
228
  // the type so the file format is stable, but do not map it anywhere.
221
229
  const languages = asStringArray(raw, "languages", file);