audit-tools 0.32.44 → 0.32.45

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 (181) hide show
  1. package/dist/audit/cli/args.d.ts.map +1 -1
  2. package/dist/audit/cli/args.js +2 -4
  3. package/dist/audit/cli/args.js.map +1 -1
  4. package/dist/audit/cli/auditStep.d.ts +1 -1
  5. package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
  6. package/dist/audit/cli/nextStepCommand.js +3 -3
  7. package/dist/audit/cli/nextStepCommand.js.map +1 -1
  8. package/dist/audit/cli/nextStepHelpers.d.ts +23 -38
  9. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  10. package/dist/audit/cli/nextStepHelpers.js +130 -89
  11. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  12. package/dist/audit/cli/resynthesizeCommand.d.ts.map +1 -1
  13. package/dist/audit/cli/resynthesizeCommand.js +8 -10
  14. package/dist/audit/cli/resynthesizeCommand.js.map +1 -1
  15. package/dist/audit/cli/rollingAuditDispatch.d.ts.map +1 -1
  16. package/dist/audit/cli/rollingAuditDispatch.js +9 -22
  17. package/dist/audit/cli/rollingAuditDispatch.js.map +1 -1
  18. package/dist/audit/contracts/wrapperResponse.d.ts +6 -6
  19. package/dist/audit/extractors/gitHistory.d.ts.map +1 -1
  20. package/dist/audit/extractors/gitHistory.js +3 -0
  21. package/dist/audit/extractors/gitHistory.js.map +1 -1
  22. package/dist/audit/orchestrator/advance.d.ts +4 -1
  23. package/dist/audit/orchestrator/advance.d.ts.map +1 -1
  24. package/dist/audit/orchestrator/advance.js +185 -36
  25. package/dist/audit/orchestrator/advance.js.map +1 -1
  26. package/dist/audit/orchestrator/fileIntegrity.d.ts.map +1 -1
  27. package/dist/audit/orchestrator/fileIntegrity.js +21 -32
  28. package/dist/audit/orchestrator/fileIntegrity.js.map +1 -1
  29. package/dist/audit/orchestrator/reviewPackets.d.ts.map +1 -1
  30. package/dist/audit/orchestrator/reviewPackets.js +31 -36
  31. package/dist/audit/orchestrator/reviewPackets.js.map +1 -1
  32. package/dist/audit/orchestrator/taskBuilder.d.ts.map +1 -1
  33. package/dist/audit/orchestrator/taskBuilder.js +12 -24
  34. package/dist/audit/orchestrator/taskBuilder.js.map +1 -1
  35. package/dist/audit/providers/opencodeProvider.d.ts +1 -4
  36. package/dist/audit/providers/opencodeProvider.d.ts.map +1 -1
  37. package/dist/audit/providers/opencodeProvider.js +6 -9
  38. package/dist/audit/providers/opencodeProvider.js.map +1 -1
  39. package/dist/audit/reporting/mergeFindings.d.ts.map +1 -1
  40. package/dist/audit/reporting/mergeFindings.js +1 -32
  41. package/dist/audit/reporting/mergeFindings.js.map +1 -1
  42. package/dist/audit/reporting/synthesis.d.ts.map +1 -1
  43. package/dist/audit/reporting/synthesis.js +1 -12
  44. package/dist/audit/reporting/synthesis.js.map +1 -1
  45. package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
  46. package/dist/audit/supervisor/operatorHandoff.js +9 -12
  47. package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
  48. package/dist/audit/supervisor/sessionConfig.d.ts.map +1 -1
  49. package/dist/audit/supervisor/sessionConfig.js +25 -35
  50. package/dist/audit/supervisor/sessionConfig.js.map +1 -1
  51. package/dist/audit/types/auditState.d.ts +6 -6
  52. package/dist/remediate/dedup/crossLensDedup.d.ts +2 -1
  53. package/dist/remediate/dedup/crossLensDedup.d.ts.map +1 -1
  54. package/dist/remediate/dedup/crossLensDedup.js +4 -33
  55. package/dist/remediate/dedup/crossLensDedup.js.map +1 -1
  56. package/dist/remediate/index.d.ts +2 -2
  57. package/dist/remediate/index.d.ts.map +1 -1
  58. package/dist/remediate/index.js +5 -8
  59. package/dist/remediate/index.js.map +1 -1
  60. package/dist/remediate/intake.d.ts.map +1 -1
  61. package/dist/remediate/intake.js +9 -5
  62. package/dist/remediate/intake.js.map +1 -1
  63. package/dist/remediate/phases/close.d.ts +59 -1
  64. package/dist/remediate/phases/close.d.ts.map +1 -1
  65. package/dist/remediate/phases/close.js +294 -29
  66. package/dist/remediate/phases/close.js.map +1 -1
  67. package/dist/remediate/phases/plan.d.ts +2 -2
  68. package/dist/remediate/phases/plan.d.ts.map +1 -1
  69. package/dist/remediate/phases/plan.js +31 -23
  70. package/dist/remediate/phases/plan.js.map +1 -1
  71. package/dist/remediate/providers/opencodeProvider.d.ts +1 -4
  72. package/dist/remediate/providers/opencodeProvider.d.ts.map +1 -1
  73. package/dist/remediate/providers/opencodeProvider.js +6 -9
  74. package/dist/remediate/providers/opencodeProvider.js.map +1 -1
  75. package/dist/remediate/state/store.d.ts +54 -11
  76. package/dist/remediate/state/store.d.ts.map +1 -1
  77. package/dist/remediate/state/store.js +37 -64
  78. package/dist/remediate/state/store.js.map +1 -1
  79. package/dist/remediate/state/types.d.ts +22 -0
  80. package/dist/remediate/state/types.d.ts.map +1 -1
  81. package/dist/remediate/state/types.js +8 -0
  82. package/dist/remediate/state/types.js.map +1 -1
  83. package/dist/remediate/steps/dispatch/acceptNode.d.ts +11 -0
  84. package/dist/remediate/steps/dispatch/acceptNode.d.ts.map +1 -1
  85. package/dist/remediate/steps/dispatch/acceptNode.js +18 -8
  86. package/dist/remediate/steps/dispatch/acceptNode.js.map +1 -1
  87. package/dist/remediate/steps/dispatch/marshal.d.ts.map +1 -1
  88. package/dist/remediate/steps/dispatch/marshal.js +30 -0
  89. package/dist/remediate/steps/dispatch/marshal.js.map +1 -1
  90. package/dist/remediate/steps/dispatch/worktreeLifecycle.d.ts.map +1 -1
  91. package/dist/remediate/steps/dispatch/worktreeLifecycle.js +12 -6
  92. package/dist/remediate/steps/dispatch/worktreeLifecycle.js.map +1 -1
  93. package/dist/remediate/steps/finalGate.d.ts +2 -2
  94. package/dist/remediate/steps/finalGate.d.ts.map +1 -1
  95. package/dist/remediate/steps/finalGate.js +8 -9
  96. package/dist/remediate/steps/finalGate.js.map +1 -1
  97. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  98. package/dist/remediate/steps/nextStep.js +44 -33
  99. package/dist/remediate/steps/nextStep.js.map +1 -1
  100. package/dist/remediate/utils/fileIntegrity.d.ts.map +1 -1
  101. package/dist/remediate/utils/fileIntegrity.js +29 -33
  102. package/dist/remediate/utils/fileIntegrity.js.map +1 -1
  103. package/dist/remediate/utils/hostAssets.d.ts +4 -1
  104. package/dist/remediate/utils/hostAssets.d.ts.map +1 -1
  105. package/dist/remediate/utils/hostAssets.js +4 -1
  106. package/dist/remediate/utils/hostAssets.js.map +1 -1
  107. package/dist/remediate/validation/artifacts.d.ts.map +1 -1
  108. package/dist/remediate/validation/artifacts.js +3 -2
  109. package/dist/remediate/validation/artifacts.js.map +1 -1
  110. package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
  111. package/dist/remediate/validation/contractPipeline.js +3 -1
  112. package/dist/remediate/validation/contractPipeline.js.map +1 -1
  113. package/dist/shared/chunkByBudget.d.ts +58 -0
  114. package/dist/shared/chunkByBudget.d.ts.map +1 -0
  115. package/dist/shared/chunkByBudget.js +31 -0
  116. package/dist/shared/chunkByBudget.js.map +1 -0
  117. package/dist/shared/countBy.d.ts +19 -0
  118. package/dist/shared/countBy.d.ts.map +1 -0
  119. package/dist/shared/countBy.js +29 -0
  120. package/dist/shared/countBy.js.map +1 -0
  121. package/dist/shared/engine/obligationEngine.d.ts +2 -2
  122. package/dist/shared/fileIntegrity.d.ts +55 -0
  123. package/dist/shared/fileIntegrity.d.ts.map +1 -0
  124. package/dist/shared/fileIntegrity.js +50 -0
  125. package/dist/shared/fileIntegrity.js.map +1 -0
  126. package/dist/shared/findingSimilarity.d.ts +26 -0
  127. package/dist/shared/findingSimilarity.d.ts.map +1 -0
  128. package/dist/shared/findingSimilarity.js +54 -0
  129. package/dist/shared/findingSimilarity.js.map +1 -0
  130. package/dist/shared/friction/stepBoundaryCapture.d.ts +17 -0
  131. package/dist/shared/friction/stepBoundaryCapture.d.ts.map +1 -1
  132. package/dist/shared/friction/stepBoundaryCapture.js +21 -0
  133. package/dist/shared/friction/stepBoundaryCapture.js.map +1 -1
  134. package/dist/shared/git.d.ts +23 -0
  135. package/dist/shared/git.d.ts.map +1 -1
  136. package/dist/shared/git.js +26 -0
  137. package/dist/shared/git.js.map +1 -1
  138. package/dist/shared/index.d.ts +11 -4
  139. package/dist/shared/index.d.ts.map +1 -1
  140. package/dist/shared/index.js +22 -3
  141. package/dist/shared/index.js.map +1 -1
  142. package/dist/shared/io/auditToolsPaths.d.ts +7 -0
  143. package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
  144. package/dist/shared/io/auditToolsPaths.js +10 -0
  145. package/dist/shared/io/auditToolsPaths.js.map +1 -1
  146. package/dist/shared/io/lockedJsonStore.d.ts +66 -0
  147. package/dist/shared/io/lockedJsonStore.d.ts.map +1 -0
  148. package/dist/shared/io/lockedJsonStore.js +59 -0
  149. package/dist/shared/io/lockedJsonStore.js.map +1 -0
  150. package/dist/shared/providers/openAiCompatibleProvider.d.ts.map +1 -1
  151. package/dist/shared/providers/openAiCompatibleProvider.js +2 -1
  152. package/dist/shared/providers/openAiCompatibleProvider.js.map +1 -1
  153. package/dist/shared/providers/opencodeProvider.d.ts +8 -0
  154. package/dist/shared/providers/opencodeProvider.d.ts.map +1 -1
  155. package/dist/shared/providers/opencodeProvider.js +10 -0
  156. package/dist/shared/providers/opencodeProvider.js.map +1 -1
  157. package/dist/shared/reporting/auditDeliverable.d.ts.map +1 -1
  158. package/dist/shared/reporting/auditDeliverable.js +14 -9
  159. package/dist/shared/reporting/auditDeliverable.js.map +1 -1
  160. package/dist/shared/tooling/exec.d.ts +42 -4
  161. package/dist/shared/tooling/exec.d.ts.map +1 -1
  162. package/dist/shared/tooling/exec.js +90 -12
  163. package/dist/shared/tooling/exec.js.map +1 -1
  164. package/dist/shared/types/contractPipeline/verification.d.ts +17 -3
  165. package/dist/shared/types/contractPipeline/verification.d.ts.map +1 -1
  166. package/dist/shared/types/sessionConfig.d.ts +28 -0
  167. package/dist/shared/types/sessionConfig.d.ts.map +1 -1
  168. package/dist/shared/types/sessionConfig.js +35 -0
  169. package/dist/shared/types/sessionConfig.js.map +1 -1
  170. package/opencode.json +2 -11
  171. package/package.json +2 -1
  172. package/scripts/audit/postinstall.mjs +57 -137
  173. package/scripts/remediate/postinstall.mjs +41 -169
  174. package/scripts/shared/install-host-assets.mjs +188 -0
  175. package/wrapper/audit-code-wrapper-lib.mjs +32 -4
  176. package/wrapper/audit-code-wrapper-opencode.mjs +41 -25
  177. package/wrapper/remediate-code-wrapper-opencode.mjs +43 -21
  178. package/dist/remediate/utils/commands.d.ts +0 -17
  179. package/dist/remediate/utils/commands.d.ts.map +0 -1
  180. package/dist/remediate/utils/commands.js +0 -29
  181. package/dist/remediate/utils/commands.js.map +0 -1
@@ -0,0 +1,188 @@
1
+ // Shared postinstall driver for the audit-code and remediate-code host-asset
2
+ // deployers (scripts/audit/postinstall.mjs, scripts/remediate/postinstall.mjs).
3
+ //
4
+ // This is a plain .mjs module: both postinstalls run at `npm install` time,
5
+ // before `tsc` has produced dist/, so this file must never import from dist/
6
+ // or from src/**/*.ts. It owns only the mechanical bits that are byte-identical
7
+ // across the two tools (I/O primitives, the install-list loop, the OpenCode
8
+ // global-config install wrapper, the Antigravity plugin block, and the final
9
+ // summary line). Each tool keeps its own permission constants and OpenCode
10
+ // config-merge functions locally — those differ in exact merge behavior
11
+ // between audit-code and remediate-code and must not be forced into parity.
12
+ import { dirname, join } from "node:path";
13
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs";
14
+
15
+ /**
16
+ * Read a source file that is required for the install to proceed at all
17
+ * (e.g. the prompt/skill markdown). Missing source is reported as a skip
18
+ * (exitCode reset to 0, not a failure) rather than thrown.
19
+ */
20
+ export function readRequiredSource(path, label, toolName) {
21
+ if (!existsSync(path)) {
22
+ console.warn(`${toolName}: ${label} source not found at ${path} - skipping global command install`);
23
+ process.exitCode = 0;
24
+ return null;
25
+ }
26
+ return readFileSync(path);
27
+ }
28
+
29
+ /** Read a source file whose absence only skips one optional install step. */
30
+ export function readOptionalSource(path, label, toolName) {
31
+ if (!existsSync(path)) {
32
+ console.warn(`${toolName}: ${label} source not found at ${path} - skipping optional install`);
33
+ return null;
34
+ }
35
+ return readFileSync(path);
36
+ }
37
+
38
+ /**
39
+ * Write a generated file, creating parent directories as needed.
40
+ * Returns "installed" on first write, "updated" on subsequent writes.
41
+ */
42
+ export function writeGeneratedFile(path, content) {
43
+ const action = existsSync(path) ? "updated" : "installed";
44
+ mkdirSync(dirname(path), { recursive: true });
45
+ writeFileSync(path, content);
46
+ return action;
47
+ }
48
+
49
+ /** Coerce an unknown value to a plain object (non-array). */
50
+ export function objectValue(value) {
51
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
52
+ }
53
+
54
+ /** Strip YAML frontmatter from a text file, returning only the body. */
55
+ export function splitFrontmatter(text) {
56
+ const normalized = text.replace(/\r\n/g, "\n");
57
+ const match = normalized.match(/^---\n[\s\S]*?\n---\n?/u);
58
+ return { body: match ? normalized.slice(match[0].length) : normalized };
59
+ }
60
+
61
+ /** Read-merge-write a JSON file. Returns "installed" or "updated". */
62
+ export function installMergedJson(path, buildMerged) {
63
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : null;
64
+ const merged = buildMerged(existing);
65
+ const action = existing ? "updated" : "installed";
66
+ mkdirSync(dirname(path), { recursive: true });
67
+ writeFileSync(path, JSON.stringify(merged, null, 2) + "\n", "utf8");
68
+ return action;
69
+ }
70
+
71
+ /**
72
+ * The scoped OpenCode permission merge helpers are single-sourced in
73
+ * audit-tools/shared (global top-level scope vs. per-agent scope). Resolve
74
+ * them best-effort: on a fresh workspace checkout the shared dist may not be
75
+ * built yet, in which case the caller skips OpenCode config deployment with a
76
+ * warning instead of failing the whole install.
77
+ */
78
+ export async function resolveSharedOpenCodePermissions() {
79
+ try {
80
+ const shared = await import("audit-tools/shared");
81
+ if (
82
+ typeof shared.mergeOpenCodeAgentPermissionRule === "function" &&
83
+ typeof shared.mergeOpenCodeGlobalPermissionRule === "function" &&
84
+ typeof shared.migrateOpenCodeGlobalExternalDirectory === "function" &&
85
+ typeof shared.withoutOpenCodeWildcard === "function"
86
+ ) {
87
+ return shared;
88
+ }
89
+ } catch {
90
+ // Leave null; the caller reports the skip.
91
+ }
92
+ return null;
93
+ }
94
+
95
+ /**
96
+ * Install a flat list of `{ label, path, sourcePath, content }` entries,
97
+ * logging one line per success and a manual-recovery block per failure.
98
+ * Mutates `counts.succeeded` / `counts.failed`.
99
+ */
100
+ export function runInstalls(toolName, installs, counts) {
101
+ for (const install of installs) {
102
+ try {
103
+ const action = writeGeneratedFile(install.path, install.content);
104
+ console.log(`${toolName}: ${action} global ${install.label} at ${install.path}`);
105
+ counts.succeeded++;
106
+ } catch (err) {
107
+ console.warn(`${toolName}: could not install global ${install.label} (${err.message})`);
108
+ console.warn(` To install manually, copy from:`);
109
+ console.warn(` ${install.sourcePath}`);
110
+ console.warn(` to:`);
111
+ console.warn(` ${install.path}`);
112
+ counts.failed++;
113
+ }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Install the merged global OpenCode config, or report the skip when the
119
+ * shared permission helpers aren't built yet. Mutates `counts`.
120
+ *
121
+ * `label` names the artifact in log lines (e.g. "OpenCode config" for
122
+ * audit-code, "OpenCode command" for remediate-code — the two tools have
123
+ * historically worded this differently). `manualInstructions`, if given, is
124
+ * an array of extra warn lines printed on failure only.
125
+ */
126
+ export function installOpenCodeGlobalConfig(
127
+ { toolName, path, sharedOpenCodePermissions, buildMerged, label, manualInstructions },
128
+ counts,
129
+ ) {
130
+ if (!sharedOpenCodePermissions) {
131
+ // Expected when audit-tools/shared isn't built yet — notably during `npm ci`
132
+ // in CI, where postinstall runs before the shared build step. This is a SKIP,
133
+ // not a failure: counting it would trip the `failed > 0` exit-1 guard. Genuine
134
+ // OpenCode write errors are still counted as failures.
135
+ console.warn(
136
+ `${toolName}: audit-tools/shared is unavailable (build the shared workspace first); skipping OpenCode config deployment`,
137
+ );
138
+ return;
139
+ }
140
+ try {
141
+ const action = installMergedJson(path, buildMerged);
142
+ console.log(`${toolName}: ${action} global ${label} in ${path}`);
143
+ counts.succeeded++;
144
+ } catch (err) {
145
+ console.warn(`${toolName}: could not install global ${label} (${err.message})`);
146
+ for (const line of manualInstructions ?? []) console.warn(line);
147
+ counts.failed++;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Install the Antigravity (Gemini IDE / Antigravity Hub) global plugin:
153
+ * a `plugin.json` manifest plus a copy of the tool's SKILL.md. Mutates
154
+ * `counts`; both writes count as a single succeeded/failed step, matching
155
+ * the tools' pre-extraction behavior.
156
+ */
157
+ export function installAntigravityPlugin(
158
+ { toolName, homeDir, pluginName, pluginVersion, skillSource },
159
+ counts,
160
+ ) {
161
+ const antigravityPluginDir = join(homeDir, ".gemini", "config", "plugins", pluginName);
162
+ const antigravityPluginJsonPath = join(antigravityPluginDir, "plugin.json");
163
+ const antigravityPluginSkillPath = join(antigravityPluginDir, "skills", "SKILL.md");
164
+ try {
165
+ const pluginJsonAction = writeGeneratedFile(
166
+ antigravityPluginJsonPath,
167
+ Buffer.from(JSON.stringify({ name: pluginName, version: pluginVersion }, null, 2) + "\n"),
168
+ );
169
+ console.log(`${toolName}: ${pluginJsonAction} Antigravity plugin manifest at ${antigravityPluginJsonPath}`);
170
+
171
+ const skillAction = writeGeneratedFile(antigravityPluginSkillPath, skillSource);
172
+ console.log(`${toolName}: ${skillAction} Antigravity plugin skill at ${antigravityPluginSkillPath}`);
173
+ counts.succeeded++;
174
+ } catch (err) {
175
+ console.warn(`${toolName}: could not install Antigravity plugin (${err.message})`);
176
+ counts.failed++;
177
+ }
178
+ }
179
+
180
+ /** Log the final summary line and set a non-zero exit code on any failure. */
181
+ export function finishPostinstall(toolName, counts, startTime) {
182
+ console.log(
183
+ `${toolName}: postinstall complete — ${counts.succeeded} succeeded, ${counts.failed} failed (${Date.now() - startTime}ms)`,
184
+ );
185
+ if (counts.failed > 0) {
186
+ process.exitCode = 1;
187
+ }
188
+ }
@@ -64,14 +64,37 @@ function selfInvocationEnv() {
64
64
  };
65
65
  }
66
66
 
67
+ // Byte-mirrors `quoteForCmd` in src/shared/tooling/exec.ts (see that file's
68
+ // doc comment for the full CVE-2024-27980 rationale: cmd.exe's own line-scan
69
+ // recognizes `& | < > ^` even inside a double-quoted region, so quote-doubling
70
+ // alone is not enough for the `.cmd`/`.bat` shim-wrapping path below; `%`
71
+ // cannot be neutralized this way at all, so it throws instead). This copy
72
+ // exists only because the wrapper runs pre-dist (bootstrap constraint) and
73
+ // cannot import the shared TS source — pinned byte-equal to the shared
74
+ // implementation by tests/shared/wrapper-quote-parity.test.mjs so the two
75
+ // copies cannot drift.
76
+ const CMD_ARGV_METACHARS = /[&|<>^]/u;
77
+
67
78
  function quoteForCmd(arg) {
68
79
  if (arg.length === 0) return '""';
69
- if (!/[\s"]/u.test(arg)) return arg;
70
- return `"${arg.replace(/"/g, '""')}"`;
80
+ if (arg.includes('%')) {
81
+ throw new Error(
82
+ `quoteForCmd: refusing to quote an argument containing "%" for a ` +
83
+ `.cmd/.bat shim invocation through cmd.exe — cmd.exe's ` +
84
+ `percent-expansion cannot be reliably neutralized by caret-escaping ` +
85
+ `(see CVE-2024-27980 and its documented residual gap). Argument: ` +
86
+ `${JSON.stringify(arg)}`,
87
+ );
88
+ }
89
+ const needsQuoting = /[\s"]/u.test(arg);
90
+ const needsMetaEscape = CMD_ARGV_METACHARS.test(arg);
91
+ if (!needsQuoting && !needsMetaEscape) return arg;
92
+ const quoted = needsQuoting ? `"${arg.replace(/"/g, '""')}"` : arg;
93
+ return needsMetaEscape ? quoted.replace(/([&|<>^])/g, '^$1') : quoted;
71
94
  }
72
95
 
73
- function resolveSpawn(command, args) {
74
- if (!(process.platform === 'win32' && /\.(cmd|bat)$/i.test(command))) {
96
+ function resolveSpawn(command, args, platform = process.platform) {
97
+ if (!(platform === 'win32' && /\.(cmd|bat)$/i.test(command))) {
75
98
  return { command, args };
76
99
  }
77
100
 
@@ -81,6 +104,11 @@ function resolveSpawn(command, args) {
81
104
  };
82
105
  }
83
106
 
107
+ // Exported for tests/shared/wrapper-quote-parity.test.mjs only (behavioral
108
+ // drift guard against src/shared/tooling/exec.ts) — not part of the wrapper's
109
+ // CLI surface.
110
+ export { quoteForCmd, resolveSpawn };
111
+
84
112
  function run(command, args, options = {}) {
85
113
  return new Promise((resolvePromise, rejectPromise) => {
86
114
  const resolved = resolveSpawn(command, args);
@@ -24,8 +24,6 @@ export const OPENCODE_AUDIT_EDIT_PERMISSION = {
24
24
  '.audit-tools/**': 'allow',
25
25
  };
26
26
 
27
- export const OPENCODE_AUDIT_EXTERNAL_DIRECTORY_PERMISSION = { '*': 'allow' };
28
-
29
27
  // Subcommands allowed for both the global bin and the dev wrapper.
30
28
  // Adding or removing a subcommand here applies to both invocation forms.
31
29
  const AUDIT_CODE_ALLOWED_SUBCOMMANDS = [
@@ -42,8 +40,10 @@ const AUDIT_CODE_WRAPPER_EXTRA_SUBCOMMANDS = ['worker-run*'];
42
40
  const AUDIT_CODE_DENIED_SUBCOMMANDS = ['synthesize*', 'cleanup*', 'requeue*', 'ingest-results*'];
43
41
 
44
42
  function buildAuditBashPermissions() {
45
- /** @type {Record<string, 'allow' | 'deny'>} */
46
- const perm = { '*': 'allow' };
43
+ // Hardened default (parity with the remediator): the wildcard is 'ask', with
44
+ // the audit-code invocations enumerated explicitly below.
45
+ /** @type {Record<string, 'allow' | 'ask' | 'deny'>} */
46
+ const perm = { '*': 'ask' };
47
47
  // Deny rules for every form of invocation (bin, dist, wrapper).
48
48
  for (const sub of AUDIT_CODE_DENIED_SUBCOMMANDS) {
49
49
  perm[`audit-code ${sub}`] = 'deny';
@@ -70,20 +70,11 @@ function buildAuditBashPermissions() {
70
70
 
71
71
  export const OPENCODE_AUDIT_BASH_PERMISSION = buildAuditBashPermissions();
72
72
 
73
- function externalDirectoryPattern(path) {
74
- return `${path.replace(/\\/g, '/').replace(/\/+$/u, '')}/**`;
75
- }
76
-
77
- function renderOpenCodeExternalDirectoryPermission() {
78
- return { '*': 'allow' };
79
- }
80
-
81
73
  export function renderOpenCodePermissionConfig() {
82
74
  return {
83
75
  read: 'allow',
84
76
  glob: 'allow',
85
77
  grep: 'allow',
86
- external_directory: renderOpenCodeExternalDirectoryPermission(),
87
78
  edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
88
79
  bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
89
80
  };
@@ -127,12 +118,17 @@ export function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
127
118
  throw new Error(`OpenCode ${label}.${tool} must be allow. Run "audit-code install --host opencode".`);
128
119
  }
129
120
  }
121
+ // Hardened shape: no external_directory allow-all (the tool no longer seeds
122
+ // the key at all; a leftover broad rule means a stale pre-hardening deploy).
130
123
  const externalDirectory = permissionConfig?.external_directory;
131
- if (!externalDirectory || typeof externalDirectory !== 'object' || Array.isArray(externalDirectory)) {
132
- throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
133
- }
134
- if (externalDirectory['*'] !== 'allow') {
135
- throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
124
+ if (
125
+ externalDirectory === 'allow' ||
126
+ (externalDirectory &&
127
+ typeof externalDirectory === 'object' &&
128
+ !Array.isArray(externalDirectory) &&
129
+ externalDirectory['*'] === 'allow')
130
+ ) {
131
+ throw new Error(`OpenCode ${label}.external_directory must not allow-all (hardened default seeds no external_directory rule). Run "audit-code install --host opencode".`);
136
132
  }
137
133
  const edit = permissionConfig?.edit;
138
134
  const bash = permissionConfig?.bash;
@@ -147,6 +143,10 @@ export function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
147
143
  if (!bash || typeof bash !== 'object' || Array.isArray(bash)) {
148
144
  throw new Error(`OpenCode ${label}.bash must allow audit-code commands. Run "audit-code install --host opencode".`);
149
145
  }
146
+ // Hardened shape: the bash wildcard is "ask" (broad "allow" was retired).
147
+ if (bash['*'] !== 'ask') {
148
+ throw new Error(`OpenCode ${label}.bash must set "*" to "ask" (hardened default; broad "allow" was retired). Run "audit-code install --host opencode".`);
149
+ }
150
150
  for (const pattern of [
151
151
  'audit-code',
152
152
  'audit-code ensure*',
@@ -184,30 +184,46 @@ export function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
184
184
  }
185
185
  }
186
186
 
187
+ // A pre-hardening deploy wrote bash['*']='allow' (the historically managed
188
+ // broad value). Migrate exactly that value away so the generated 'ask' seed
189
+ // wins on regeneration; any other user-authored wildcard survives untouched.
190
+ function withoutManagedBroadBashWildcard(rule) {
191
+ const existing = objectValue(rule);
192
+ if (existing['*'] !== 'allow') {
193
+ return rule;
194
+ }
195
+ return withoutOpenCodeWildcard(existing);
196
+ }
197
+
187
198
  function mergePermissionBlock(existingPermission, generatedPermission) {
188
199
  const existing = objectValue(existingPermission);
189
- return {
200
+ const merged = {
190
201
  ...generatedPermission,
191
202
  ...existing,
192
203
  read: generatedPermission.read,
193
204
  glob: generatedPermission.glob,
194
205
  grep: generatedPermission.grep,
195
- external_directory: mergeOpenCodeAgentPermissionRule(
196
- existing.external_directory,
197
- generatedPermission.external_directory,
198
- OPENCODE_AUDIT_EXTERNAL_DIRECTORY_PERMISSION,
199
- ),
200
206
  edit: mergeOpenCodeAgentPermissionRule(
201
207
  existing.edit,
202
208
  generatedPermission.edit,
203
209
  withoutOpenCodeWildcard(OPENCODE_AUDIT_EDIT_PERMISSION),
204
210
  ),
205
211
  bash: mergeOpenCodeAgentPermissionRule(
206
- existing.bash,
212
+ withoutManagedBroadBashWildcard(existing.bash),
207
213
  generatedPermission.bash,
208
214
  withoutOpenCodeWildcard(OPENCODE_AUDIT_BASH_PERMISSION),
209
215
  ),
210
216
  };
217
+ // Hardened default: no external_directory rule is seeded. A leftover
218
+ // historically managed allow-all is migrated away; any non-matching
219
+ // user-authored value is preserved untouched.
220
+ const externalDirectory = migrateOpenCodeGlobalExternalDirectory(existing.external_directory);
221
+ if (externalDirectory === undefined) {
222
+ delete merged.external_directory;
223
+ } else {
224
+ merged.external_directory = externalDirectory;
225
+ }
226
+ return merged;
211
227
  }
212
228
 
213
229
  // Collect every agent's bash rule set from a merged agent map (in stable,
@@ -3,6 +3,7 @@
3
3
  // buildMergedOpenCodeProjectConfig throws a clear error in that case.
4
4
  let mergeOpenCodeAgentPermissionRule;
5
5
  let withoutOpenCodeWildcard;
6
+ let migrateOpenCodeGlobalExternalDirectory;
6
7
  let composeOpenCodeBashCeiling;
7
8
  export let sharedOpenCodePermissions = false;
8
9
 
@@ -10,6 +11,7 @@ try {
10
11
  const shared = await import('audit-tools/shared');
11
12
  mergeOpenCodeAgentPermissionRule = shared.mergeOpenCodeAgentPermissionRule;
12
13
  withoutOpenCodeWildcard = shared.withoutOpenCodeWildcard;
14
+ migrateOpenCodeGlobalExternalDirectory = shared.migrateOpenCodeGlobalExternalDirectory;
13
15
  composeOpenCodeBashCeiling = shared.composeOpenCodeBashCeiling;
14
16
  sharedOpenCodePermissions = true;
15
17
  } catch {
@@ -22,8 +24,6 @@ export const OPENCODE_REMEDIATE_EDIT_PERMISSION = {
22
24
  '.audit-tools/**': 'allow',
23
25
  };
24
26
 
25
- export const OPENCODE_REMEDIATE_EXTERNAL_DIRECTORY_PERMISSION = { '*': 'allow' };
26
-
27
27
  // Subcommands allowed for both the global bin and the dev wrapper.
28
28
  // Adding or removing a subcommand here applies to both invocation forms.
29
29
  const REMEDIATE_CODE_ALLOWED_SUBCOMMANDS = [
@@ -36,8 +36,10 @@ const REMEDIATE_CODE_ALLOWED_SUBCOMMANDS = [
36
36
  ];
37
37
 
38
38
  function buildRemediateBashPermissions() {
39
- /** @type {Record<string, 'allow' | 'deny'>} */
40
- const perm = { '*': 'allow' };
39
+ // Hardened default: the wildcard is 'ask', with the remediate-code
40
+ // invocations enumerated explicitly below.
41
+ /** @type {Record<string, 'allow' | 'ask' | 'deny'>} */
42
+ const perm = { '*': 'ask' };
41
43
  // Allow rules: bare bin and each subcommand for both the global bin and wrapper.
42
44
  perm['remediate-code'] = 'allow';
43
45
  for (const sub of REMEDIATE_CODE_ALLOWED_SUBCOMMANDS) {
@@ -55,16 +57,11 @@ function buildRemediateBashPermissions() {
55
57
 
56
58
  export const OPENCODE_REMEDIATE_BASH_PERMISSION = buildRemediateBashPermissions();
57
59
 
58
- function renderOpenCodeExternalDirectoryPermission() {
59
- return { '*': 'allow' };
60
- }
61
-
62
60
  export function renderOpenCodePermissionConfig() {
63
61
  return {
64
62
  read: 'allow',
65
63
  glob: 'allow',
66
64
  grep: 'allow',
67
- external_directory: renderOpenCodeExternalDirectoryPermission(),
68
65
  edit: { ...OPENCODE_REMEDIATE_EDIT_PERMISSION },
69
66
  bash: { ...OPENCODE_REMEDIATE_BASH_PERMISSION },
70
67
  };
@@ -108,12 +105,17 @@ export function assertOpenCodeRemediatePermissionConfig(permissionConfig, label)
108
105
  throw new Error(`OpenCode ${label}.${tool} must be allow. Run "remediate-code install --host opencode".`);
109
106
  }
110
107
  }
108
+ // Hardened shape: no external_directory allow-all (the tool no longer seeds
109
+ // the key at all; a leftover broad rule means a stale pre-hardening deploy).
111
110
  const externalDirectory = permissionConfig?.external_directory;
112
- if (!externalDirectory || typeof externalDirectory !== 'object' || Array.isArray(externalDirectory)) {
113
- throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "remediate-code install --host opencode".`);
114
- }
115
- if (externalDirectory['*'] !== 'allow') {
116
- throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "remediate-code install --host opencode".`);
111
+ if (
112
+ externalDirectory === 'allow' ||
113
+ (externalDirectory &&
114
+ typeof externalDirectory === 'object' &&
115
+ !Array.isArray(externalDirectory) &&
116
+ externalDirectory['*'] === 'allow')
117
+ ) {
118
+ throw new Error(`OpenCode ${label}.external_directory must not allow-all (hardened default seeds no external_directory rule). Run "remediate-code install --host opencode".`);
117
119
  }
118
120
  const edit = permissionConfig?.edit;
119
121
  const bash = permissionConfig?.bash;
@@ -128,6 +130,10 @@ export function assertOpenCodeRemediatePermissionConfig(permissionConfig, label)
128
130
  if (!bash || typeof bash !== 'object' || Array.isArray(bash)) {
129
131
  throw new Error(`OpenCode ${label}.bash must allow remediate-code commands. Run "remediate-code install --host opencode".`);
130
132
  }
133
+ // Hardened shape: the bash wildcard is "ask" (broad "allow" was retired).
134
+ if (bash['*'] !== 'ask') {
135
+ throw new Error(`OpenCode ${label}.bash must set "*" to "ask" (hardened default; broad "allow" was retired). Run "remediate-code install --host opencode".`);
136
+ }
131
137
  for (const pattern of [
132
138
  'remediate-code',
133
139
  'remediate-code ensure*',
@@ -146,30 +152,46 @@ export function assertOpenCodeRemediatePermissionConfig(permissionConfig, label)
146
152
  }
147
153
  }
148
154
 
155
+ // A pre-hardening deploy wrote bash['*']='allow' (the historically managed
156
+ // broad value). Migrate exactly that value away so the generated 'ask' seed
157
+ // wins on regeneration; any other user-authored wildcard survives untouched.
158
+ function withoutManagedBroadBashWildcard(rule) {
159
+ const existing = objectValue(rule);
160
+ if (existing['*'] !== 'allow') {
161
+ return rule;
162
+ }
163
+ return withoutOpenCodeWildcard(existing);
164
+ }
165
+
149
166
  function mergePermissionBlock(existingPermission, generatedPermission) {
150
167
  const existing = objectValue(existingPermission);
151
- return {
168
+ const merged = {
152
169
  ...generatedPermission,
153
170
  ...existing,
154
171
  read: generatedPermission.read,
155
172
  glob: generatedPermission.glob,
156
173
  grep: generatedPermission.grep,
157
- external_directory: mergeOpenCodeAgentPermissionRule(
158
- existing.external_directory,
159
- generatedPermission.external_directory,
160
- OPENCODE_REMEDIATE_EXTERNAL_DIRECTORY_PERMISSION,
161
- ),
162
174
  edit: mergeOpenCodeAgentPermissionRule(
163
175
  existing.edit,
164
176
  generatedPermission.edit,
165
177
  withoutOpenCodeWildcard(OPENCODE_REMEDIATE_EDIT_PERMISSION),
166
178
  ),
167
179
  bash: mergeOpenCodeAgentPermissionRule(
168
- existing.bash,
180
+ withoutManagedBroadBashWildcard(existing.bash),
169
181
  generatedPermission.bash,
170
182
  withoutOpenCodeWildcard(OPENCODE_REMEDIATE_BASH_PERMISSION),
171
183
  ),
172
184
  };
185
+ // Hardened default: no external_directory rule is seeded. A leftover
186
+ // historically managed allow-all is migrated away; any non-matching
187
+ // user-authored value is preserved untouched.
188
+ const externalDirectory = migrateOpenCodeGlobalExternalDirectory(existing.external_directory);
189
+ if (externalDirectory === undefined) {
190
+ delete merged.external_directory;
191
+ } else {
192
+ merged.external_directory = externalDirectory;
193
+ }
194
+ return merged;
173
195
  }
174
196
 
175
197
  // Collect every agent's bash rule set from a merged agent map (in stable,
@@ -1,17 +0,0 @@
1
- import { spawnSync, type SpawnSyncOptions } from "node:child_process";
2
- import { quoteForCmd, platformCommand, type RunTrackedOptions, type RunTrackedResult } from "audit-tools/shared";
3
- type SpawnResult = ReturnType<typeof spawnSync>;
4
- export { quoteForCmd, platformCommand };
5
- /**
6
- * Run a command synchronously by argv. Delegates to the shared `runTracked`,
7
- * which applies the single Windows `.cmd`/`.bat` wrapping implementation and
8
- * never uses `shell: true`.
9
- */
10
- export declare function runCommand(command: string, args: string[], options?: RunTrackedOptions): RunTrackedResult;
11
- /**
12
- * Run a stored command *string* (e.g. a configured `test_command`) through the
13
- * platform shell. argv-based callers should use `runCommand`/`runTracked`;
14
- * this exists only for opaque single-string commands that need shell parsing.
15
- */
16
- export declare function runShellCommand(command: string, options?: SpawnSyncOptions): SpawnResult;
17
- //# sourceMappingURL=commands.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../src/remediate/utils/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAEL,WAAW,EACX,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAKhD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAElB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB,GAC7B,WAAW,CAQb"}
@@ -1,29 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import { runTracked, quoteForCmd, platformCommand, } from "audit-tools/shared";
3
- // The Windows wrapping/quoting logic now lives in `audit-tools/shared`
4
- // (tooling/exec.ts) so the auditor and remediator share one implementation.
5
- // These re-exports preserve the import surface callers (and tests) rely on.
6
- export { quoteForCmd, platformCommand };
7
- /**
8
- * Run a command synchronously by argv. Delegates to the shared `runTracked`,
9
- * which applies the single Windows `.cmd`/`.bat` wrapping implementation and
10
- * never uses `shell: true`.
11
- */
12
- export function runCommand(command, args, options = {}) {
13
- return runTracked([command, ...args], options);
14
- }
15
- /**
16
- * Run a stored command *string* (e.g. a configured `test_command`) through the
17
- * platform shell. argv-based callers should use `runCommand`/`runTracked`;
18
- * this exists only for opaque single-string commands that need shell parsing.
19
- */
20
- export function runShellCommand(command, options = {}) {
21
- return spawnSync(command, {
22
- ...options,
23
- shell: true,
24
- // Default-hide the console window a windowless parent would pop on win32;
25
- // an explicit caller value still wins.
26
- windowsHide: options.windowsHide ?? true,
27
- });
28
- }
29
- //# sourceMappingURL=commands.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/remediate/utils/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyB,MAAM,oBAAoB,CAAC;AACtE,OAAO,EACL,UAAU,EACV,WAAW,EACX,eAAe,GAGhB,MAAM,oBAAoB,CAAC;AAI5B,uEAAuE;AACvE,4EAA4E;AAC5E,4EAA4E;AAC5E,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,IAAc,EACd,UAA6B,EAAE;IAE/B,OAAO,UAAU,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,UAA4B,EAAE;IAE9B,OAAO,SAAS,CAAC,OAAO,EAAE;QACxB,GAAG,OAAO;QACV,KAAK,EAAE,IAAI;QACX,0EAA0E;QAC1E,uCAAuC;QACvC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;KACzC,CAAC,CAAC;AACL,CAAC"}