deepdebug-local-agent 1.0.22 → 1.1.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 (188) hide show
  1. package/.claude/context.md +76 -0
  2. package/.claude/skills/README.md +25 -0
  3. package/.claude/skills/tdd-debugging/SKILL.md +53 -0
  4. package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
  5. package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
  6. package/.claude/standards/README.md +51 -0
  7. package/.claude/standards/clean-code.md +75 -0
  8. package/.claude/standards/code-review-checklist.md +61 -0
  9. package/.claude/standards/layered-architecture.md +163 -0
  10. package/.claude/standards/node.md +45 -0
  11. package/.claude/standards/security-owasp.md +114 -0
  12. package/.claude/standards/simplicity.md +42 -0
  13. package/.claude/standards/solid.md +64 -0
  14. package/.claude/standards/testing.md +59 -0
  15. package/.cursor/rules/standards.mdc +17 -0
  16. package/.github/workflows/build-byoc-image.yml +315 -0
  17. package/.github/workflows/deploy-gcp.yml +102 -0
  18. package/.github/workflows/npm-publish.yml +69 -8
  19. package/AGENTS.md +21 -0
  20. package/CHANGELOG.md +69 -0
  21. package/CLAUDE.md +13 -0
  22. package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
  23. package/Dockerfile +4 -0
  24. package/Dockerfile.full +55 -3
  25. package/README.md +81 -0
  26. package/bin/install.js +16 -5
  27. package/cloudbuild-full.yaml +57 -0
  28. package/docker-compose.yml +5 -3
  29. package/docs/OBSERVABILITY.md +164 -0
  30. package/docs/byoc-agent-image.md +70 -0
  31. package/docs/graphify-packaging-plan.md +133 -0
  32. package/docs/graphify-validation-run-2026-10-28.md +243 -0
  33. package/docs/legacy-connectors.md +268 -0
  34. package/docs/releasing.md +43 -0
  35. package/docs/signed-commands.md +163 -0
  36. package/env.example +30 -0
  37. package/helm/Chart.yaml +2 -2
  38. package/helm/templates/deployment.yaml +13 -3
  39. package/helm/templates/networkpolicy.yaml +22 -0
  40. package/helm/templates/secret.yaml +1 -0
  41. package/helm/values.yaml +13 -14
  42. package/mcp-server.js +4 -1
  43. package/package.json +12 -4
  44. package/scripts/sync-fixtures.mjs +246 -0
  45. package/scripts/upgrade-observability-push.sh +227 -0
  46. package/src/adapters/as400/as400-parser.js +12 -0
  47. package/src/adapters/as400/index.js +134 -0
  48. package/src/adapters/cobol/cobol-parser.js +37 -0
  49. package/src/adapters/cobol/index.js +151 -0
  50. package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
  51. package/src/adapters/ibmmq/index.js +137 -0
  52. package/src/adapters/index.js +79 -0
  53. package/src/adapters/oracle-forms/index.js +145 -0
  54. package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
  55. package/src/adapters/sap/index.js +146 -0
  56. package/src/adapters/sap/sap-parser.js +41 -0
  57. package/src/ai-engine.js +22 -22
  58. package/src/config/trusted-signing-keys.json +3 -0
  59. package/src/detectors/build-tool-detector.js +50 -0
  60. package/src/detectors/multi-project-detector.js +22 -22
  61. package/src/detectors/service-detector.js +24 -24
  62. package/src/exec-utils.js +26 -4
  63. package/src/git/base-git-provider.js +117 -117
  64. package/src/git/bitbucket-provider.js +2 -2
  65. package/src/git/git-provider-registry.js +18 -18
  66. package/src/git/github-provider.js +13 -13
  67. package/src/git/multi-repo-pr.js +161 -0
  68. package/src/mcp-http-server.js +32 -16
  69. package/src/observability/bundle-files.js +418 -0
  70. package/src/observability/detect.js +151 -0
  71. package/src/observability/provision.js +212 -0
  72. package/src/observability/render-cli.js +59 -0
  73. package/src/observability/routes.js +88 -0
  74. package/src/patch/patch-engine.js +28 -28
  75. package/src/patch-manager.js +85 -85
  76. package/src/patch_bkp.js +14 -14
  77. package/src/ports.js +4 -4
  78. package/src/routes/workspace.route.js +47 -16
  79. package/src/runtimes/base-runtime.js +75 -75
  80. package/src/runtimes/java/gradle-runtime.js +17 -17
  81. package/src/runtimes/java/java-integrations.js +25 -25
  82. package/src/runtimes/java/maven-runtime.js +34 -34
  83. package/src/runtimes/node/node-integrations.js +1 -1
  84. package/src/runtimes/node/npm-runtime.js +14 -14
  85. package/src/runtimes/node/yarn-runtime.js +4 -4
  86. package/src/runtimes/runtime-registry.js +25 -25
  87. package/src/server-local.js +4 -4
  88. package/src/server.js +1412 -335
  89. package/src/siblings/burst-suppressor.js +89 -0
  90. package/src/siblings/capabilities.js +273 -0
  91. package/src/siblings/env-compat.cjs +58 -0
  92. package/src/siblings/incident-forwarder.js +320 -0
  93. package/src/siblings/logger.js +15 -0
  94. package/src/siblings/metrics.js +94 -0
  95. package/src/utils/agent-auth.js +184 -0
  96. package/src/utils/approval-token.js +56 -0
  97. package/src/utils/bind-host.js +29 -0
  98. package/src/utils/branch-refspec.js +98 -0
  99. package/src/utils/child-env.js +34 -0
  100. package/src/utils/environment-diagnostics.js +55 -55
  101. package/src/utils/exec-policy.js +962 -0
  102. package/src/utils/exec-utils.js +15 -15
  103. package/src/utils/file-count.js +46 -0
  104. package/src/utils/fs-utils.js +10 -10
  105. package/src/utils/multi-repo-open.js +132 -0
  106. package/src/utils/open-gate.js +183 -0
  107. package/src/utils/ranged-read.js +65 -0
  108. package/src/utils/request-guards.js +77 -0
  109. package/src/utils/request-signing.js +342 -0
  110. package/src/utils/root-recovery.js +244 -0
  111. package/src/utils/safe-exec-args.js +58 -0
  112. package/src/utils/sandbox.js +182 -0
  113. package/src/utils/search-outcome.js +56 -0
  114. package/src/utils/workspace-root.js +80 -0
  115. package/src/validators/syntax-validator.js +149 -0
  116. package/src/workspace/detect-port.js +16 -16
  117. package/src/workspace/file-reader.js +1 -1
  118. package/src/workspace/process-manager.js +46 -46
  119. package/src/workspace-manager.js +86 -24
  120. package/test/adapters/as400/as400.test.js +121 -0
  121. package/test/adapters/cobol/cobol.test.js +217 -0
  122. package/test/adapters/ibmmq/ibmmq.test.js +122 -0
  123. package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
  124. package/test/adapters/registry.test.js +108 -0
  125. package/test/adapters/sap/sap.test.js +142 -0
  126. package/test/agent-auth.test.js +194 -0
  127. package/test/bind-host.test.js +26 -0
  128. package/test/branch-refspec.test.js +224 -0
  129. package/test/burst-suppressor.test.js +60 -0
  130. package/test/capabilities-access.test.js +47 -0
  131. package/test/capabilities.test.js +87 -0
  132. package/test/child-env.test.js +23 -0
  133. package/test/env-compat.test.js +71 -0
  134. package/test/exec-policy.test.js +463 -0
  135. package/test/file-count.test.js +64 -0
  136. package/test/incident-forwarder.test.js +231 -0
  137. package/test/multi-repo-open.test.js +180 -0
  138. package/test/multi-repo-pr.test.js +129 -0
  139. package/test/observability-detect.test.js +199 -0
  140. package/test/open-gate.test.js +341 -0
  141. package/test/ranged-read.test.js +60 -0
  142. package/test/request-guards.test.js +91 -0
  143. package/test/request-signing.test.js +284 -0
  144. package/test/root-recovery-sidecar.test.js +171 -0
  145. package/test/root-recovery.test.js +274 -0
  146. package/test/safe-exec-args.test.js +58 -0
  147. package/test/sandbox.test.js +98 -0
  148. package/test/search-outcome.test.js +50 -0
  149. package/test/workspace-root.test.js +64 -0
  150. package/tools/graphify-validation/README.md +92 -0
  151. package/tools/graphify-validation/config/questions.json +31 -0
  152. package/tools/graphify-validation/config/repos.json +9 -0
  153. package/tools/graphify-validation/config/symbols.json +6 -0
  154. package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
  155. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
  156. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
  157. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
  158. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
  159. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
  160. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
  161. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
  162. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
  163. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
  164. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
  165. package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
  166. package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
  167. package/tools/graphify-validation/lib/graph_metrics.py +96 -0
  168. package/tools/graphify-validation/lib/gv_common.py +130 -0
  169. package/tools/graphify-validation/lib/licenses.py +47 -0
  170. package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
  171. package/tools/graphify-validation/lib/recall.py +115 -0
  172. package/tools/graphify-validation/lib/report.py +226 -0
  173. package/tools/graphify-validation/lib/run_all.py +237 -0
  174. package/tools/graphify-validation/lib/run_graphify.py +144 -0
  175. package/tools/graphify-validation/lib/rusage_helper.py +17 -0
  176. package/tools/graphify-validation/lib/sample_edges.py +246 -0
  177. package/tools/graphify-validation/lib/spring_fixture.py +81 -0
  178. package/tools/graphify-validation/requirements-py311.lock +508 -0
  179. package/tools/graphify-validation/requirements-tools.in +1 -0
  180. package/tools/graphify-validation/requirements-tools.lock +371 -0
  181. package/tools/graphify-validation/requirements.in +1 -0
  182. package/tools/graphify-validation/requirements.lock +502 -0
  183. package/tools/graphify-validation/run.sh +43 -0
  184. package/tools/graphify-validation/tests/test_harness.py +151 -0
  185. package/cloudbuild-agent-qa.yaml +0 -43
  186. package/cloudbuild-agent-staging.yaml +0 -40
  187. package/cloudbuild.yaml.deprecated +0 -42
  188. package/src/local-agent-source.tar.gz +0 -0
@@ -4,7 +4,7 @@ import { run } from '../utils/exec-utils.js';
4
4
  /**
5
5
  * GitHubProvider
6
6
  *
7
- * Implementação do Git provider para GitHub.
7
+ * Git provider implementation for GitHub.
8
8
  */
9
9
  export class GitHubProvider extends BaseGitProvider {
10
10
 
@@ -170,7 +170,7 @@ export class GitHubProvider extends BaseGitProvider {
170
170
 
171
171
  const cloneUrl = this.getCloneUrl(owner, repo, true);
172
172
 
173
- // Inserir token na URL para autenticação
173
+ // Insert the token into the URL for authentication
174
174
  const authedUrl = cloneUrl.replace(
175
175
  'https://',
176
176
  `https://${this.accessToken}@`
@@ -212,16 +212,16 @@ export class GitHubProvider extends BaseGitProvider {
212
212
  }
213
213
 
214
214
  async createBranch(owner, repo, branchName, fromRef) {
215
- // Obter SHA do ref de origem
215
+ // Get the SHA of the source ref
216
216
  let sha = fromRef;
217
217
 
218
218
  if (!fromRef.match(/^[a-f0-9]{40}$/)) {
219
- // É um nome de branch, precisamos do SHA
219
+ // It is a branch name, we need the SHA
220
220
  const branch = await this.getBranch(owner, repo, fromRef);
221
221
  sha = branch.sha;
222
222
  }
223
223
 
224
- // Criar a referência
224
+ // Create the reference
225
225
  const data = await this.apiRequest(`/repos/${owner}/${repo}/git/refs`, {
226
226
  method: 'POST',
227
227
  body: JSON.stringify({
@@ -268,17 +268,17 @@ export class GitHubProvider extends BaseGitProvider {
268
268
  }
269
269
 
270
270
  async createCommit(owner, repo, branch, message, changes) {
271
- // 1. Obter referência atual
271
+ // 1. Get the current reference
272
272
  const branchData = await this.getBranch(owner, repo, branch);
273
273
  const baseSha = branchData.sha;
274
274
 
275
- // 2. Obter tree atual
275
+ // 2. Get the current tree
276
276
  const baseCommit = await this.apiRequest(
277
277
  `/repos/${owner}/${repo}/git/commits/${baseSha}`
278
278
  );
279
279
  const baseTreeSha = baseCommit.tree.sha;
280
280
 
281
- // 3. Criar blobs para cada arquivo modificado
281
+ // 3. Create blobs for each modified file
282
282
  const treeItems = [];
283
283
 
284
284
  for (const change of changes) {
@@ -290,7 +290,7 @@ export class GitHubProvider extends BaseGitProvider {
290
290
  sha: null
291
291
  });
292
292
  } else {
293
- // Criar blob
293
+ // Create a blob
294
294
  const blob = await this.apiRequest(`/repos/${owner}/${repo}/git/blobs`, {
295
295
  method: 'POST',
296
296
  body: JSON.stringify({
@@ -308,7 +308,7 @@ export class GitHubProvider extends BaseGitProvider {
308
308
  }
309
309
  }
310
310
 
311
- // 4. Criar nova tree
311
+ // 4. Create a new tree
312
312
  const newTree = await this.apiRequest(`/repos/${owner}/${repo}/git/trees`, {
313
313
  method: 'POST',
314
314
  body: JSON.stringify({
@@ -317,7 +317,7 @@ export class GitHubProvider extends BaseGitProvider {
317
317
  })
318
318
  });
319
319
 
320
- // 5. Criar commit
320
+ // 5. Create the commit
321
321
  const newCommit = await this.apiRequest(`/repos/${owner}/${repo}/git/commits`, {
322
322
  method: 'POST',
323
323
  body: JSON.stringify({
@@ -327,7 +327,7 @@ export class GitHubProvider extends BaseGitProvider {
327
327
  })
328
328
  });
329
329
 
330
- // 6. Atualizar referência da branch
330
+ // 6. Update the branch reference
331
331
  await this.apiRequest(`/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
332
332
  method: 'PATCH',
333
333
  body: JSON.stringify({
@@ -676,7 +676,7 @@ export class GitHubProvider extends BaseGitProvider {
676
676
  } catch {
677
677
  // Fallback: add a reply indicating resolution
678
678
  await this.replyToPRComment(owner, repo, prNumber, commentId,
679
- '✅ Resolved by InspTech AI — fix has been applied.');
679
+ '✅ Resolved by VisionHammer — fix has been applied.');
680
680
  return { resolved: true };
681
681
  }
682
682
  }
@@ -0,0 +1,161 @@
1
+ // Multi-repo coordinated PR helpers.
2
+ //
3
+ // A Workbench multi-repo session clones N repos UNDER ONE per-workspace parent
4
+ // dir (see POST /workspace/open cloud-multi-clone). The parent dir is NOT itself
5
+ // a git repo; each immediate sub-dir is. This module holds the PURE, unit-tested
6
+ // helpers used by /workspace/git/create-fix-branch to open ONE PR per CHANGED
7
+ // repo on a SHARED branch name, and cross-link the sibling PRs in each body.
8
+ //
9
+ // Single-repo behavior is untouched: server.js only routes here when the
10
+ // workspace root is a multi-repo parent AND the feature flag is enabled.
11
+
12
+ import fs from "fs";
13
+ import path from "path";
14
+
15
+ /** Feature flag. Default ON; set MULTI_REPO_PR_ENABLED=false to disable. */
16
+ export function multiRepoPrEnabled(env = process.env) {
17
+ return String(env.MULTI_REPO_PR_ENABLED ?? "true").toLowerCase() !== "false";
18
+ }
19
+
20
+ /**
21
+ * Immediate sub-directories of `root` that are themselves git repos
22
+ * (contain a `.git` entry). Returns [{ dir, path }] sorted by dir name for
23
+ * stable ordering. Never throws — an unreadable root yields [].
24
+ */
25
+ export function listRepoSubdirs(root, fsImpl = fs) {
26
+ if (!root) return [];
27
+ let entries;
28
+ try {
29
+ entries = fsImpl.readdirSync(root, { withFileTypes: true });
30
+ } catch {
31
+ return [];
32
+ }
33
+ const repos = [];
34
+ for (const e of entries) {
35
+ if (!e.isDirectory()) continue;
36
+ const repoPath = path.join(root, e.name);
37
+ let hasGit = false;
38
+ try {
39
+ hasGit = fsImpl.existsSync(path.join(repoPath, ".git"));
40
+ } catch {
41
+ hasGit = false;
42
+ }
43
+ if (hasGit) repos.push({ dir: e.name, path: repoPath });
44
+ }
45
+ repos.sort((a, b) => a.dir.localeCompare(b.dir));
46
+ return repos;
47
+ }
48
+
49
+ /**
50
+ * A "multi-repo root" is a dir that is NOT a git repo itself but has >= 1
51
+ * git sub-repo directly under it. When the root IS a git repo (single-repo
52
+ * clone) this returns false and the caller keeps the legacy single-repo path.
53
+ */
54
+ export function isMultiRepoRoot(root, fsImpl = fs) {
55
+ if (!root) return false;
56
+ let rootIsRepo = false;
57
+ try {
58
+ rootIsRepo = fsImpl.existsSync(path.join(root, ".git"));
59
+ } catch {
60
+ rootIsRepo = false;
61
+ }
62
+ if (rootIsRepo) return false;
63
+ return listRepoSubdirs(root, fsImpl).length > 0;
64
+ }
65
+
66
+ /**
67
+ * Parse an authenticated git remote URL into its parts.
68
+ * Handles https URLs with embedded userinfo, e.g.
69
+ * https://x-access-token:TOKEN@github.com/owner/repo.git
70
+ * https://oauth2:TOKEN@gitlab.com/group/repo.git
71
+ * https://user:appPassword@bitbucket.org/ws/repo.git
72
+ * https://github.com/owner/repo (no auth)
73
+ * Returns { host, provider, ownerRepo, userinfo, apiToken } or null if unparseable.
74
+ * apiToken = the token component for REST auth (part after the first ':' in
75
+ * userinfo when present, else the whole userinfo).
76
+ */
77
+ export function parseAuthedRemote(remoteUrl) {
78
+ if (!remoteUrl || typeof remoteUrl !== "string") return null;
79
+ let u = remoteUrl.trim();
80
+ // Normalize scp-like SSH form git@host:owner/repo.git -> https://host/owner/repo.git
81
+ const sshMatch = u.match(/^([^@]+)@([^:]+):(.+)$/);
82
+ if (sshMatch && !u.includes("://")) {
83
+ u = `https://${sshMatch[2]}/${sshMatch[3]}`;
84
+ }
85
+ const m = u.match(/^https?:\/\/(?:([^@/]+)@)?([^/]+)\/(.+?)(?:\.git)?\/?$/i);
86
+ if (!m) return null;
87
+ const userinfo = m[1] || "";
88
+ const host = m[2];
89
+ const ownerRepo = m[3];
90
+ let provider = "generic";
91
+ if (/github\./i.test(host)) provider = "github";
92
+ else if (/gitlab\./i.test(host)) provider = "gitlab";
93
+ else if (/bitbucket\./i.test(host)) provider = "bitbucket";
94
+ let apiToken = "";
95
+ if (userinfo) {
96
+ const ci = userinfo.indexOf(":");
97
+ apiToken = ci >= 0 ? userinfo.slice(ci + 1) : userinfo;
98
+ }
99
+ return { host, provider, ownerRepo, userinfo, apiToken };
100
+ }
101
+
102
+ /** Strip any embedded userinfo (credentials) from an https git URL for display. */
103
+ export function stripRemoteCreds(remoteUrl) {
104
+ if (!remoteUrl) return "";
105
+ return remoteUrl.replace(/^(https?:\/\/)[^@/]+@/i, "$1").replace(/\.git$/i, "");
106
+ }
107
+
108
+ /**
109
+ * Build a "compare"/"new MR" web URL for a pushed branch when no API PR link
110
+ * is available (non-GitHub providers, or API failure). cleanBase = the repo web
111
+ * URL without creds or .git.
112
+ */
113
+ export function buildCompareUrl(cleanBase, provider, branch, targetBranch) {
114
+ if (!cleanBase) return "";
115
+ switch (provider) {
116
+ case "gitlab":
117
+ return `${cleanBase}/-/merge_requests/new?merge_request[source_branch]=${branch}` +
118
+ `&merge_request[target_branch]=${targetBranch}`;
119
+ case "bitbucket":
120
+ return `${cleanBase}/pull-requests/new?source=${branch}&dest=${targetBranch}`;
121
+ case "github":
122
+ default:
123
+ return `${cleanBase}/compare/${targetBranch}...${branch}?expand=1`;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Cross-link note appended to each PR body: "part of a multi-repo change" plus
129
+ * links to the SIBLING PRs (all but the one identified by selfDir). Returns ""
130
+ * when there are no siblings with a usable url.
131
+ */
132
+ export function buildCrossLinkNote(selfDir, prs) {
133
+ if (!Array.isArray(prs)) return "";
134
+ const siblings = prs.filter((p) => p && p.dir !== selfDir && p.prUrl);
135
+ if (siblings.length === 0) return "";
136
+ const links = siblings.map((p) => `- ${p.dir}: ${p.prUrl}`).join("\n");
137
+ return `\n\n---\nPart of a multi-repo change. Sibling pull requests:\n${links}\n`;
138
+ }
139
+
140
+ const VH_TRAILER = "\nGenerated by VisionHammer Agent\n";
141
+
142
+ /**
143
+ * Base PR body for one repo of a coordinated multi-repo change (provider-neutral,
144
+ * ASCII-only). Sibling links are appended later via buildCrossLinkNote once every
145
+ * PR exists (two-pass), so this body is self-consistent even if a sibling fails.
146
+ */
147
+ export function buildMultiRepoPrBody({ dir, branch, commitMessage, filesChanged, commitSha }) {
148
+ const lines = [
149
+ "## Coordinated multi-repo change",
150
+ "",
151
+ `**Repo dir:** \`${dir}\``,
152
+ `**Branch:** \`${branch}\``,
153
+ `**Files changed:** ${filesChanged}`,
154
+ ];
155
+ if (commitSha) lines.push(`**Commit:** ${String(commitSha).substring(0, 8)}`);
156
+ if (commitMessage) {
157
+ lines.push("", "### Summary", commitMessage);
158
+ }
159
+ lines.push("", "---", VH_TRAILER.trim());
160
+ return lines.join("\n");
161
+ }
@@ -1,4 +1,8 @@
1
1
  import express from "express";
2
+ import { sanitizedChildEnv } from "./utils/child-env.js";
3
+ import { createAuthMiddleware } from "./utils/agent-auth.js";
4
+ import { getSigningConfig, createSignatureMiddleware, captureRawBody } from "./utils/request-signing.js";
5
+ import { decideExec, rejectionBody, resolveExecPolicyConfig } from "./utils/exec-policy.js";
2
6
  import path from "path";
3
7
  import { spawn } from "child_process";
4
8
  import { readFile, writeFile, listRecursive, exists } from "./fs-utils.js";
@@ -6,22 +10,30 @@ import { detectProject } from "./detectors.js";
6
10
  import { applyUnifiedDiff } from "./patch.js";
7
11
 
8
12
  /**
9
- * MCP HTTP Bridge — Expõe as MCP tools como REST API.
13
+ * MCP HTTP Bridge — Exposes the MCP tools as a REST API.
10
14
  *
11
- * O Gateway Java chama estes endpoints para obter contexto do código
12
- * ANTES de enviar o prompt ao Claude.
15
+ * The Java Gateway calls these endpoints to get code context
16
+ * BEFORE sending the prompt to Claude.
13
17
  *
14
- * Port: 5056 (separado do Express principal na 5055)
18
+ * Port: 5056 (separate from the main Express server on 5055)
15
19
  *
16
- * Isto é mais prático que stdio MCP para comunicação Gateway ↔ Agent,
17
- * e mantém compatibilidade com MCP tools para uso futuro com Claude Code.
20
+ * This is more practical than stdio MCP for Gateway ↔ Agent communication,
21
+ * and keeps compatibility with MCP tools for future use with Claude Code.
18
22
  */
19
23
 
20
24
  const IGNORE_DIRS = ["node_modules", ".git", "target", "build", "dist", ".idea", "__pycache__", "vendor", ".gradle"];
21
25
 
22
26
  export function startMCPHttpServer(workspaceManager, port = 5056) {
23
27
  const app = express();
24
- app.use(express.json({ limit: "50mb" }));
28
+ // SECURITY: this bridge runs shell commands (/mcp/execute-command) and
29
+ // writes files. Its only consumer is the in-process WS tunnel, so it binds
30
+ // to loopback and sits behind the same auth perimeter as the main API.
31
+ app.use(createAuthMiddleware());
32
+ app.use(express.json({ limit: "50mb", verify: captureRawBody }));
33
+ // Same signed-command verification as the main API (shared nonce cache).
34
+ const signing = getSigningConfig();
35
+ const execPolicy = resolveExecPolicyConfig();
36
+ app.use(createSignatureMiddleware(signing));
25
37
 
26
38
  // ========================================
27
39
  // Health
@@ -175,14 +187,16 @@ export function startMCPHttpServer(workspaceManager, port = 5056) {
175
187
  const { workspaceId, command, timeout = 120 } = req.body;
176
188
  if (!command) return res.status(400).json({ error: "command required" });
177
189
 
178
- // Security
179
- const BLOCKED = ["rm -rf /", "mkfs", "dd if=/dev", ":(){ :|:"];
180
- if (BLOCKED.some(b => command.includes(b))) {
181
- return res.status(403).json({ error: "Command blocked" });
182
- }
183
-
184
190
  try {
185
191
  const root = workspaceManager.resolveRoot(workspaceId);
192
+ // Security: allowlist (utils/exec-policy.js) instead of the old blocklist.
193
+ const decision = decideExec({
194
+ command, root, headers: req.headers, body: req.body || {},
195
+ vhSignature: req.vhSignature, signingCfg: signing,
196
+ }, execPolicy);
197
+ if (!decision.allow) {
198
+ return res.status(decision.status || 403).json(rejectionBody(decision));
199
+ }
186
200
  const result = await execInWorkspace(root, command, timeout);
187
201
  res.json({ ok: result.exitCode === 0, ...result });
188
202
  } catch (e) {
@@ -243,8 +257,9 @@ export function startMCPHttpServer(workspaceManager, port = 5056) {
243
257
  // ========================================
244
258
  // START SERVER
245
259
  // ========================================
246
- app.listen(port, () => {
247
- console.log(`🔌 MCP HTTP Bridge listening on http://localhost:${port}`);
260
+ const host = process.env.MCP_BIND_HOST || "127.0.0.1";
261
+ app.listen(port, host, () => {
262
+ console.log(`🔌 MCP HTTP Bridge listening on http://${host}:${port}`);
248
263
  });
249
264
 
250
265
  return app;
@@ -288,7 +303,8 @@ function grepWorkspace(root, query, filePattern, maxResults) {
288
303
 
289
304
  function execInWorkspace(root, command, timeoutSec) {
290
305
  return new Promise((resolve) => {
291
- const child = spawn("sh", ["-c", command], { cwd: root });
306
+ // SECURITY (2026-10-11): model-authored command; strip the agent's secrets.
307
+ const child = spawn("sh", ["-c", command], { cwd: root, env: sanitizedChildEnv(process.env) });
292
308
  let stdout = "";
293
309
  let stderr = "";
294
310