@quantakrypto/core 0.2.2 → 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 (173) 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 +70 -2
  36. package/dist/detect-utils.d.ts.map +1 -1
  37. package/dist/detect-utils.js +139 -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/pem.d.ts +4 -0
  56. package/dist/detectors/pem.d.ts.map +1 -1
  57. package/dist/detectors/pem.js +113 -91
  58. package/dist/detectors/pem.js.map +1 -1
  59. package/dist/detectors/python.d.ts +26 -0
  60. package/dist/detectors/python.d.ts.map +1 -0
  61. package/dist/detectors/python.js +171 -0
  62. package/dist/detectors/python.js.map +1 -0
  63. package/dist/detectors/ruby.d.ts +15 -0
  64. package/dist/detectors/ruby.d.ts.map +1 -0
  65. package/dist/detectors/ruby.js +75 -0
  66. package/dist/detectors/ruby.js.map +1 -0
  67. package/dist/detectors/rust.d.ts +13 -0
  68. package/dist/detectors/rust.d.ts.map +1 -0
  69. package/dist/detectors/rust.js +94 -0
  70. package/dist/detectors/rust.js.map +1 -0
  71. package/dist/detectors/source.d.ts +9 -2
  72. package/dist/detectors/source.d.ts.map +1 -1
  73. package/dist/detectors/source.js +435 -286
  74. package/dist/detectors/source.js.map +1 -1
  75. package/dist/index.d.ts +19 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +16 -2
  78. package/dist/index.js.map +1 -1
  79. package/dist/parallel.d.ts +4 -0
  80. package/dist/parallel.d.ts.map +1 -1
  81. package/dist/parallel.js +61 -8
  82. package/dist/parallel.js.map +1 -1
  83. package/dist/patch-policy.d.ts +22 -0
  84. package/dist/patch-policy.d.ts.map +1 -0
  85. package/dist/patch-policy.js +21 -0
  86. package/dist/patch-policy.js.map +1 -0
  87. package/dist/redact.d.ts +19 -0
  88. package/dist/redact.d.ts.map +1 -0
  89. package/dist/redact.js +77 -0
  90. package/dist/redact.js.map +1 -0
  91. package/dist/registry.d.ts +29 -4
  92. package/dist/registry.d.ts.map +1 -1
  93. package/dist/registry.js +62 -4
  94. package/dist/registry.js.map +1 -1
  95. package/dist/remediate-pipeline.d.ts +41 -0
  96. package/dist/remediate-pipeline.d.ts.map +1 -0
  97. package/dist/remediate-pipeline.js +38 -0
  98. package/dist/remediate-pipeline.js.map +1 -0
  99. package/dist/remediate-request.d.ts +21 -0
  100. package/dist/remediate-request.d.ts.map +1 -0
  101. package/dist/remediate-request.js +28 -0
  102. package/dist/remediate-request.js.map +1 -0
  103. package/dist/remediation.d.ts +10 -0
  104. package/dist/remediation.d.ts.map +1 -1
  105. package/dist/remediation.js +21 -8
  106. package/dist/remediation.js.map +1 -1
  107. package/dist/report.d.ts +9 -1
  108. package/dist/report.d.ts.map +1 -1
  109. package/dist/report.js +72 -24
  110. package/dist/report.js.map +1 -1
  111. package/dist/scan-worker.js +7 -3
  112. package/dist/scan-worker.js.map +1 -1
  113. package/dist/scan.d.ts +6 -5
  114. package/dist/scan.d.ts.map +1 -1
  115. package/dist/scan.js +59 -30
  116. package/dist/scan.js.map +1 -1
  117. package/dist/triage.d.ts +27 -0
  118. package/dist/triage.d.ts.map +1 -0
  119. package/dist/triage.js +33 -0
  120. package/dist/triage.js.map +1 -0
  121. package/dist/types.d.ts +106 -3
  122. package/dist/types.d.ts.map +1 -1
  123. package/dist/verify.d.ts +30 -0
  124. package/dist/verify.d.ts.map +1 -0
  125. package/dist/verify.js +65 -0
  126. package/dist/verify.js.map +1 -0
  127. package/dist/version.d.ts +1 -1
  128. package/dist/version.js +1 -1
  129. package/dist/walk.d.ts +7 -0
  130. package/dist/walk.d.ts.map +1 -1
  131. package/dist/walk.js +85 -11
  132. package/dist/walk.js.map +1 -1
  133. package/dist/worktree.d.ts +6 -0
  134. package/dist/worktree.d.ts.map +1 -0
  135. package/dist/worktree.js +43 -0
  136. package/dist/worktree.js.map +1 -0
  137. package/package.json +1 -1
  138. package/src/agent-types.ts +52 -0
  139. package/src/cache.ts +120 -0
  140. package/src/cbom.ts +27 -1
  141. package/src/changed.ts +13 -4
  142. package/src/codemods/config-toggle.ts +35 -0
  143. package/src/codemods/registry.ts +31 -0
  144. package/src/comments.ts +180 -0
  145. package/src/config.ts +8 -0
  146. package/src/dependencies.ts +483 -51
  147. package/src/detect-utils.ts +180 -16
  148. package/src/detectors/c.ts +132 -0
  149. package/src/detectors/csharp.ts +98 -0
  150. package/src/detectors/go.ts +167 -0
  151. package/src/detectors/java.ts +235 -0
  152. package/src/detectors/pem.ts +124 -105
  153. package/src/detectors/python.ts +212 -0
  154. package/src/detectors/ruby.ts +97 -0
  155. package/src/detectors/rust.ts +112 -0
  156. package/src/detectors/source.ts +535 -364
  157. package/src/index.ts +47 -2
  158. package/src/parallel.ts +72 -7
  159. package/src/patch-policy.ts +42 -0
  160. package/src/redact.ts +92 -0
  161. package/src/registry.ts +71 -5
  162. package/src/remediate-pipeline.ts +81 -0
  163. package/src/remediate-request.ts +52 -0
  164. package/src/remediation.ts +27 -11
  165. package/src/report.ts +106 -24
  166. package/src/scan-worker.ts +18 -7
  167. package/src/scan.ts +74 -29
  168. package/src/triage.ts +58 -0
  169. package/src/types.ts +120 -3
  170. package/src/verify.ts +82 -0
  171. package/src/version.ts +1 -1
  172. package/src/walk.ts +81 -12
  173. package/src/worktree.ts +45 -0
package/src/verify.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Snippet-level fix verification: run the detectors over a piece of code (NOT
3
+ * the filesystem) and report any classical crypto that remains. This is the
4
+ * deterministic gate a fix must pass — the same logic the MCP `verify_fix` tool
5
+ * and the remediation pipeline both use, so they can never disagree on what
6
+ * "the finding is gone" means.
7
+ */
8
+ import type { Finding } from "./types.js";
9
+ import { detectFile, detectors } from "./scan.js";
10
+
11
+ /**
12
+ * Map a language name (or a bare extension) to a source extension whose
13
+ * detectors we run. Returns null for languages the scanner does not analyze.
14
+ */
15
+ export function languageToExtension(language: string): string | null {
16
+ const l = language.trim().toLowerCase().replace(/^\./, "");
17
+ const map: Record<string, string> = {
18
+ js: ".js",
19
+ javascript: ".js",
20
+ jsx: ".jsx",
21
+ ts: ".ts",
22
+ typescript: ".ts",
23
+ tsx: ".tsx",
24
+ mjs: ".mjs",
25
+ cjs: ".cjs",
26
+ py: ".py",
27
+ python: ".py",
28
+ go: ".go",
29
+ golang: ".go",
30
+ java: ".java",
31
+ kotlin: ".kt",
32
+ kt: ".kt",
33
+ cs: ".cs",
34
+ csharp: ".cs",
35
+ "c#": ".cs",
36
+ dotnet: ".cs",
37
+ rs: ".rs",
38
+ rust: ".rs",
39
+ rb: ".rb",
40
+ ruby: ".rb",
41
+ c: ".c",
42
+ "c++": ".cpp",
43
+ cpp: ".cpp",
44
+ cc: ".cc",
45
+ h: ".h",
46
+ hpp: ".hpp",
47
+ };
48
+ return map[l] ?? null;
49
+ }
50
+
51
+ /** Result of {@link verifyFix}: the findings that remain, and whether the
52
+ * language is one the scanner actually analyzes (so `findings: []` on an
53
+ * unsupported language is NOT a clean verification). */
54
+ export interface VerifyResult {
55
+ supported: boolean;
56
+ findings: Finding[];
57
+ }
58
+
59
+ /**
60
+ * Run all detectors over `code`, selecting them by `filename` (extension) or
61
+ * `language`. Pure: no I/O. When neither identifies an analyzable language,
62
+ * `supported` is false and the empty findings list must not be read as "fixed".
63
+ */
64
+ export function verifyFix(
65
+ code: string,
66
+ opts: { filename?: string; language?: string },
67
+ ): VerifyResult {
68
+ let name: string;
69
+ if (opts.filename && opts.filename.trim()) {
70
+ name = opts.filename.trim();
71
+ } else if (opts.language && opts.language.trim()) {
72
+ const ext = languageToExtension(opts.language);
73
+ if (!ext) return { supported: false, findings: [] };
74
+ name = `snippet${ext}`;
75
+ } else {
76
+ return { supported: false, findings: [] };
77
+ }
78
+ const dotExt = name.replace(/^.*(\.[^.]+)$/, "$1");
79
+ const supported = languageToExtension(dotExt) !== null;
80
+ const findings = detectFile(name, code, detectors, { source: true, config: true, deps: true });
81
+ return { supported, findings };
82
+ }
package/src/version.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * the scan orchestrator can import it without creating a cycle through index.ts.
4
4
  * Keep in sync with packages/core/package.json.
5
5
  */
6
- export const VERSION = "0.2.2";
6
+ export const VERSION = "0.4.0";
package/src/walk.ts CHANGED
@@ -8,6 +8,8 @@ import { readdir, stat } from "node:fs/promises";
8
8
  import type { Dirent } from "node:fs";
9
9
  import * as path from "node:path";
10
10
 
11
+ import { isManifestFile } from "./dependencies.js";
12
+
11
13
  /** Directories ignored by default (can be disabled with noDefaultIgnores). */
12
14
  export const DEFAULT_IGNORES: readonly string[] = [
13
15
  "node_modules",
@@ -116,12 +118,80 @@ export function toPosix(p: string): string {
116
118
  return p.split(path.sep).join("/");
117
119
  }
118
120
 
119
- /** True if `rel` (a POSIX relative path) matches any pattern (substring/prefix). */
120
- function matchesAny(rel: string, patterns: readonly string[]): boolean {
121
+ /** A pattern is treated as a glob when it contains one of these metacharacters. */
122
+ function hasGlobMeta(pattern: string): boolean {
123
+ return /[*?[]/.test(pattern);
124
+ }
125
+
126
+ /**
127
+ * Translate a glob into an anchored RegExp: `*` matches within a path segment
128
+ * (not `/`), `**` matches across segments, a leading `**` before a slash is an
129
+ * optional path prefix, `?` matches a single non-`/` char, `[...]` is a
130
+ * character class. Other regex
131
+ * metacharacters are escaped.
132
+ */
133
+ function globToRegExp(glob: string): RegExp {
134
+ let re = "";
135
+ for (let i = 0; i < glob.length; i++) {
136
+ const c = glob[i];
137
+ if (c === "*") {
138
+ if (glob[i + 1] === "*") {
139
+ i++; // consume the second '*'
140
+ if (glob[i + 1] === "/") {
141
+ i++; // consume the trailing '/'
142
+ re += "(?:.*/)?"; // zero or more path segments
143
+ } else {
144
+ re += ".*"; // '**' spanning segments
145
+ }
146
+ } else {
147
+ re += "[^/]*"; // '*' within a segment
148
+ }
149
+ } else if (c === "?") {
150
+ re += "[^/]";
151
+ } else if (c === "[") {
152
+ // Pass a character class through; find its closing ']'.
153
+ const end = glob.indexOf("]", i + 1);
154
+ if (end === -1) {
155
+ re += "\\[";
156
+ } else {
157
+ re += glob.slice(i, end + 1);
158
+ i = end;
159
+ }
160
+ } else if ("\\^$.|+(){}".includes(c)) {
161
+ re += "\\" + c;
162
+ } else {
163
+ re += c;
164
+ }
165
+ }
166
+ return new RegExp(`^${re}$`);
167
+ }
168
+
169
+ /** Memoized glob→RegExp so a pattern is compiled once, not once per file. */
170
+ const GLOB_CACHE = new Map<string, RegExp>();
171
+ function globRegExp(pattern: string): RegExp {
172
+ let re = GLOB_CACHE.get(pattern);
173
+ if (!re) {
174
+ re = globToRegExp(pattern);
175
+ GLOB_CACHE.set(pattern, re);
176
+ }
177
+ return re;
178
+ }
179
+
180
+ /**
181
+ * True if `rel` (a POSIX relative path) matches any pattern. A pattern with a
182
+ * glob metacharacter (`*`, `?`, `[`) is matched as an anchored glob; a plain
183
+ * pattern keeps the historical substring / path-prefix semantics (so `"src"`
184
+ * still matches `src/a.ts` and `"secrets"` matches anywhere).
185
+ */
186
+ export function matchesAny(rel: string, patterns: readonly string[]): boolean {
121
187
  for (const pattern of patterns) {
122
188
  if (!pattern) continue;
123
189
  const p = toPosix(pattern).replace(/\/+$/, "");
124
- // Substring match (handles "src/legacy" or "secrets")...
190
+ if (hasGlobMeta(p)) {
191
+ if (globRegExp(p).test(rel)) return true;
192
+ continue;
193
+ }
194
+ // Plain pattern: substring match (handles "src/legacy" or "secrets")...
125
195
  if (rel.includes(p)) return true;
126
196
  // ...and explicit path-prefix match ("foo" should match "foo/bar.ts").
127
197
  if (rel === p || rel.startsWith(`${p}/`)) return true;
@@ -232,16 +302,12 @@ interface WalkContext {
232
302
  * scanned for vulnerable dependencies instead of being silently dropped.
233
303
  */
234
304
  function passesSizeLimit(rel: string, size: number, maxFileSize: number): boolean {
235
- if (isManifestPath(rel)) return true;
305
+ // Dependency manifests are always read (they can exceed the size cap but carry
306
+ // the whole dependency tree). Uses the single {@link isManifestFile} definition.
307
+ if (isManifestFile(rel)) return true;
236
308
  return size <= maxFileSize;
237
309
  }
238
310
 
239
- /** True if the path's basename is a dependency manifest we always read. */
240
- function isManifestPath(rel: string): boolean {
241
- const base = rel.split("/").pop() ?? rel;
242
- return base === "package.json" || base === "package-lock.json";
243
- }
244
-
245
311
  /** Internal recursive directory walker. `relDir` is POSIX-relative to the root. */
246
312
  async function* walkDir(absDir: string, relDir: string, ctx: WalkContext): AsyncGenerator<string> {
247
313
  let entries: Dirent[];
@@ -274,8 +340,11 @@ async function* walkDir(absDir: string, relDir: string, ctx: WalkContext): Async
274
340
  if (!entry.isFile()) continue;
275
341
  if (isExcluded(rel, ctx.exclude)) continue;
276
342
  if (!isIncluded(rel, ctx.include)) continue;
277
- if (isBinaryPath(rel)) continue;
278
- if (isGeneratedPath(rel)) continue;
343
+ // Dependency manifests (incl. yarn.lock / pnpm-lock.yaml) are always read —
344
+ // they carry the dependency tree and must not be dropped as binary/generated.
345
+ const manifest = isManifestFile(rel);
346
+ if (!manifest && isBinaryPath(rel)) continue;
347
+ if (!manifest && isGeneratedPath(rel)) continue;
279
348
 
280
349
  try {
281
350
  const s = await stat(abs);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Ephemeral git worktree runner. Remediation applies candidate patches inside a
3
+ * throwaway `git worktree` checked out at HEAD, so the user's working tree is
4
+ * never touched while patches are being tried and verified. The worktree is
5
+ * always removed afterwards, even if the callback throws.
6
+ */
7
+ import { execFile } from "node:child_process";
8
+ import { promisify } from "node:util";
9
+ import { mkdtemp, rm } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+
13
+ const exec = promisify(execFile);
14
+
15
+ async function git(args: string[], cwd?: string): Promise<void> {
16
+ await exec("git", args, { cwd });
17
+ }
18
+
19
+ /**
20
+ * Create a detached worktree of `repoRoot` at HEAD, run `fn` with its path, and
21
+ * always tear it down. Throws if `repoRoot` is not a git repository.
22
+ */
23
+ export async function withWorktree<T>(
24
+ repoRoot: string,
25
+ fn: (dir: string) => Promise<T>,
26
+ ): Promise<T> {
27
+ try {
28
+ await git(["rev-parse", "--is-inside-work-tree"], repoRoot);
29
+ } catch {
30
+ throw new Error(`withWorktree: "${repoRoot}" is not a git repository`);
31
+ }
32
+ const base = await mkdtemp(join(tmpdir(), "quantakrypto-wt-"));
33
+ const dir = join(base, "wt");
34
+ await git(["worktree", "add", "--detach", dir], repoRoot);
35
+ try {
36
+ return await fn(dir);
37
+ } finally {
38
+ try {
39
+ await git(["worktree", "remove", "--force", dir], repoRoot);
40
+ } catch {
41
+ // best effort — the temp dir removal below still reclaims the disk.
42
+ }
43
+ await rm(base, { recursive: true, force: true });
44
+ }
45
+ }