From c9cf6380a6c9e80496eb66809eb02c5e328b8db8 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Mon, 14 Sep 2026 03:21:27 +0700 Subject: [PATCH 1/2] Allow host-local OpenFn bridge listeners Signed-off-by: Jeremi Joslin --- services/event-bridge/README.md | 1 + services/event-bridge/src/server.js | 6 ++++-- services/event-bridge/test/bridge.test.js | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/services/event-bridge/README.md b/services/event-bridge/README.md index 2882dc6..5e2e93d 100644 --- a/services/event-bridge/README.md +++ b/services/event-bridge/README.md @@ -32,6 +32,7 @@ Configuration is required unless a default is listed: | `BREG_HMAC_KEY_FILE` | File containing exact HMAC key bytes, at least 32 bytes. | | `BREG_EXPECTED_SOURCE` | Exact CloudEvent source for the registry instance. | | `BREG_EVENT_PATH` | Optional exact signed ingress path, default `/events/breg`; one lowercase name under `/events/`, with letters, digits or hyphens. | +| `BREG_BIND_HOST` | Listener address, default `0.0.0.0` for compatibility. Set `127.0.0.1` for a host-local receiver; no other addresses are accepted. | | `BREG_EXPECTED_ENTITY` | Exact payload entity, `farm` for this pilot. | | `BREG_EXPECTED_EVENTS_FILE` | JSON object mapping each exact type to `{ "schema": "exact-dataschema", "trigger": "created" }`; `patched` and `request_lifecycle` are also supported. Optional per-event `entity` and `valueFields` override defaults; CLI mode requires `effect`. | | `BREG_ALLOWED_VALUE_FIELDS_FILE` | JSON array of exact projected string fields, `["local-identifier"]` for this pilot. | diff --git a/services/event-bridge/src/server.js b/services/event-bridge/src/server.js index c3261cb..2d97297 100644 --- a/services/event-bridge/src/server.js +++ b/services/event-bridge/src/server.js @@ -29,6 +29,7 @@ export function loadConfig(env = process.env) { try { const hmacKey = readFileSync(env.BREG_HMAC_KEY_FILE); const eventPath = env.BREG_EVENT_PATH ?? EVENT_PATH; + const bindHost = env.BREG_BIND_HOST ?? '0.0.0.0'; const deliveryMode = env.OPENFN_DELIVERY_MODE ?? 'webhook'; if (!['webhook', 'cli'].includes(deliveryMode)) throw new Error('invalid delivery mode'); const apiKey = deliveryMode === 'webhook' ? readFileSync(env.OPENFN_API_KEY_FILE, 'utf8') : undefined; @@ -36,6 +37,7 @@ export function loadConfig(env = process.env) { const allowedValueFields = JSON.parse(readFileSync(env.BREG_ALLOWED_VALUE_FIELDS_FILE, 'utf8')); const url = deliveryMode === 'webhook' ? new URL(env.OPENFN_WEBHOOK_URL) : undefined; if (hmacKey.length < 32 || !/^\/events\/[a-z0-9][a-z0-9-]{0,63}$/.test(eventPath) || + !['0.0.0.0', '127.0.0.1'].includes(bindHost) || (apiKey !== undefined && !/^[\x21-\x7e]+$/.test(apiKey)) || !env.BREG_EXPECTED_SOURCE || !env.BREG_EXPECTED_ENTITY || !object(expectedEvents) || Object.keys(expectedEvents).length === 0 || @@ -58,7 +60,7 @@ export function loadConfig(env = process.env) { hmacKey, apiKey, expectedEvents, allowedValueFields, deliveryMode, inboxPath: env.OPENFN_INBOX_PATH, expectedSource: env.BREG_EXPECTED_SOURCE, expectedEntity: env.BREG_EXPECTED_ENTITY, - eventPath, + eventPath, bindHost, openfnUrl: url, port: positive(env.PORT, 8081, 65535), maxBodyBytes: positive(env.MAX_BODY_BYTES, 65536, 1048576), maxDeliverySkewSeconds: positive(env.MAX_DELIVERY_SKEW_SECONDS, 300, 3600), @@ -248,7 +250,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) const config = loadConfig(); const server = createBridge(config); server.on('error', () => { process.stderr.write('bridge unavailable\n'); process.exitCode = 1; }); - server.listen(config.port, '0.0.0.0'); + server.listen(config.port, config.bindHost); for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => server.close()); } catch { process.stderr.write('invalid bridge configuration\n'); process.exitCode = 1; } } diff --git a/services/event-bridge/test/bridge.test.js b/services/event-bridge/test/bridge.test.js index bd77b50..a77ed23 100644 --- a/services/event-bridge/test/bridge.test.js +++ b/services/event-bridge/test/bridge.test.js @@ -198,6 +198,10 @@ test('configuration uses exact secret bytes, explicit HTTP trust and safe errors assert.throws(() => loadConfig(env), /^Error: invalid bridge configuration$/); assert.equal(loadConfig({ ...env, ALLOW_HTTP: 'true' }).hmacKey.at(-1), 10); assert.equal(loadConfig({ ...env, ALLOW_HTTP: 'true', BREG_EVENT_PATH: '/events/laboratory' }).eventPath, '/events/laboratory'); + assert.equal(loadConfig({ ...env, ALLOW_HTTP: 'true' }).bindHost, '0.0.0.0'); + assert.equal(loadConfig({ ...env, ALLOW_HTTP: 'true', BREG_BIND_HOST: '127.0.0.1' }).bindHost, '127.0.0.1'); + assert.throws(() => loadConfig({ ...env, ALLOW_HTTP: 'true', BREG_BIND_HOST: 'example.com' }), + /^Error: invalid bridge configuration$/); assert.throws(() => loadConfig({ ...env, ALLOW_HTTP: 'true', BREG_EVENT_PATH: '/events/laboratory?other=1' }), /^Error: invalid bridge configuration$/); assert.throws(() => loadConfig({ ...env, OPENFN_WEBHOOK_URL: 'https://secret:canary@example.org/' }), /^Error: invalid bridge configuration$/); From 81366748bad5b8076a1e209eafb19044a6461aa0 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Mon, 14 Sep 2026 03:50:14 +0700 Subject: [PATCH 2/2] Document host-local bridge listener option Signed-off-by: Jeremi Joslin --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd3cab9..78c8b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Added + +- The event bridge accepts `BREG_BIND_HOST=127.0.0.1` for host-local receivers. + Its existing `0.0.0.0` default remains unchanged. + ### Removed - Removed the Relay v1 adaptor (`@openfn/language-registry-relay`), its