|
22 | 22 | // idempotent server-side). |
23 | 23 |
|
24 | 24 | const fs = require('fs'); |
25 | | -const http = require('http'); |
26 | | -const https = require('https'); |
27 | 25 | const crypto = require('crypto'); |
28 | 26 |
|
29 | 27 | const { computeAssetId } = require('../gep/contentHash'); |
30 | | -const { enforceHubScheme, strictHttpsAgent } = require('../gep/hubFetch'); |
| 28 | +const { hubFetch } = require('../gep/hubFetch'); |
31 | 29 | const { |
32 | 30 | getNodeId, |
33 | 31 | getHubUrl, |
@@ -126,77 +124,35 @@ function _publishUrl() { |
126 | 124 | return base + '/a2a/publish'; |
127 | 125 | } |
128 | 126 |
|
129 | | -function _postJson(urlStr, body, timeoutMs) { |
130 | | - return new Promise(function (resolve) { |
131 | | - // Same TLS posture as hubFetch: refuse plain http:// unless |
132 | | - // EVOMAP_HUB_ALLOW_INSECURE=1. Before this guard the function |
133 | | - // silently fell back to `lib = http` for any non-https URL, so an |
134 | | - // operator override `A2A_HUB_URL=http://...` would send /a2a/publish |
135 | | - // and /a2a/task/complete in cleartext while hubFetch-routed calls |
136 | | - // (e.g. /a2a/verify-solidify) refused the same URL — inconsistent |
137 | | - // TLS enforcement across modules. |
138 | | - try { |
139 | | - enforceHubScheme(urlStr); |
140 | | - } catch (e) { |
141 | | - resolve({ ok: false, error: 'tls_refused: ' + (e && e.message) }); |
142 | | - return; |
143 | | - } |
144 | | - let parsed; |
145 | | - try { |
146 | | - parsed = new URL(urlStr); |
147 | | - } catch (e) { |
148 | | - resolve({ ok: false, error: 'invalid_url: ' + (e && e.message) }); |
149 | | - return; |
150 | | - } |
151 | | - const isHttps = parsed.protocol === 'https:'; |
152 | | - const lib = isHttps ? https : http; |
153 | | - const payload = JSON.stringify(body || {}); |
154 | | - const headers = Object.assign( |
155 | | - { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, |
156 | | - buildHubHeaders() || {}, |
157 | | - ); |
158 | | - // Pin TLS cert verification for https calls so a globally-disabled |
159 | | - // NODE_TLS_REJECT_UNAUTHORIZED=0 cannot weaken the Hub channel |
160 | | - // (Cursor Security Reviewer #160 Medium). hubFetch enforces the |
161 | | - // same via its undici dispatcher; this is the Node-native-https |
162 | | - // equivalent. |
163 | | - // |
164 | | - // Skipped under EVOMAP_HUB_ALLOW_INSECURE=1 so local-dev / self- |
165 | | - // signed mock hubs that legitimately rely on |
166 | | - // NODE_TLS_REJECT_UNAUTHORIZED=0 still work. |
167 | | - const requestOpts = { |
168 | | - hostname: parsed.hostname, |
169 | | - port: parsed.port || (isHttps ? 443 : 80), |
170 | | - path: parsed.pathname + (parsed.search || ''), |
| 127 | +async function _postJson(urlStr, body, timeoutMs) { |
| 128 | + const payload = JSON.stringify(body || {}); |
| 129 | + const headers = Object.assign( |
| 130 | + { 'Content-Type': 'application/json' }, |
| 131 | + buildHubHeaders() || {}, |
| 132 | + ); |
| 133 | + |
| 134 | + try { |
| 135 | + const res = await hubFetch(urlStr, { |
171 | 136 | method: 'POST', |
172 | 137 | headers: headers, |
173 | | - timeout: timeoutMs || PUBLISH_TIMEOUT_MS, |
174 | | - }; |
175 | | - if (isHttps && process.env.EVOMAP_HUB_ALLOW_INSECURE !== '1') { |
176 | | - requestOpts.agent = strictHttpsAgent; |
| 138 | + body: payload, |
| 139 | + signal: AbortSignal.timeout(timeoutMs || PUBLISH_TIMEOUT_MS), |
| 140 | + }); |
| 141 | + const text = await res.text(); |
| 142 | + let data = null; |
| 143 | + try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; } |
| 144 | + if (res.status >= 200 && res.status < 300) { |
| 145 | + return { ok: true, status: res.status, data }; |
177 | 146 | } |
178 | | - const req = lib.request( |
179 | | - requestOpts, |
180 | | - function (res) { |
181 | | - const chunks = []; |
182 | | - res.on('data', function (c) { chunks.push(c); }); |
183 | | - res.on('end', function () { |
184 | | - const text = Buffer.concat(chunks).toString('utf8'); |
185 | | - let data = null; |
186 | | - try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; } |
187 | | - if (res.statusCode >= 200 && res.statusCode < 300) { |
188 | | - resolve({ ok: true, status: res.statusCode, data }); |
189 | | - } else { |
190 | | - resolve({ ok: false, status: res.statusCode, data, error: 'http_' + res.statusCode }); |
191 | | - } |
192 | | - }); |
193 | | - }, |
194 | | - ); |
195 | | - req.on('timeout', function () { req.destroy(new Error('timeout')); }); |
196 | | - req.on('error', function (err) { resolve({ ok: false, error: err.message }); }); |
197 | | - req.write(payload); |
198 | | - req.end(); |
199 | | - }); |
| 147 | + return { ok: false, status: res.status, data, error: 'http_' + res.status }; |
| 148 | + } catch (err) { |
| 149 | + const msg = (err && err.message) || String(err); |
| 150 | + if (msg.indexOf('[hubFetch]') !== -1) { |
| 151 | + if (/not a valid URL/i.test(msg)) return { ok: false, error: 'invalid_url: ' + msg }; |
| 152 | + return { ok: false, error: 'tls_refused: ' + msg }; |
| 153 | + } |
| 154 | + return { ok: false, error: msg }; |
| 155 | + } |
200 | 156 | } |
201 | 157 |
|
202 | 158 | async function _ensureNodeSecret() { |
|
0 commit comments