From 226868002fae1745222914e35a9fe4552d6d5642 Mon Sep 17 00:00:00 2001 From: Vladimir Upirov <4648606+Valodya@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:52:20 +0300 Subject: [PATCH] - BL-134 Add a header to any HTTP request sent by JS custom logic --- lib/cli/defaults.json | 1 + lib/server-code/runners/tasks/sandbox.js | 93 +++++++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/lib/cli/defaults.json b/lib/cli/defaults.json index af12b547..31305dc0 100644 --- a/lib/cli/defaults.json +++ b/lib/cli/defaults.json @@ -13,6 +13,7 @@ "codeInspectionTimeout": 60 } }, + "enableAnalyticsHeader": true, "redis": { "bl/production": {}, "analytics": {} diff --git a/lib/server-code/runners/tasks/sandbox.js b/lib/server-code/runners/tasks/sandbox.js index 2c07bcf6..ce22becf 100644 --- a/lib/server-code/runners/tasks/sandbox.js +++ b/lib/server-code/runners/tasks/sandbox.js @@ -6,11 +6,14 @@ exports.processSetuid = process.setuid.bind(process) let applied = false -exports.apply = applicationId => { +const ANALYTICS_HEADER_KEY = 'X-Backendless-CodeRunner-App-ID' + +exports.apply = (applicationId, enableAnalyticsHeader) => { if (applied) { return } + // Block dangerous process methods process.send = getThrower('Calling process.send') process.kill = getThrower('Calling process.kill') @@ -20,6 +23,63 @@ exports.apply = applicationId => { child_process.ChildProcess = getThrower('Class ChildProcess') + if (enableAnalyticsHeader) { + // Intercept HTTP/HTTPS requests while blocking server creation + const http = require('http') + const https = require('https') + + // Intercept outgoing HTTP requests + const originalHttpRequest = http.request + const originalHttpGet = http.get + const originalHttpsRequest = https.request + const originalHttpsGet = https.get + + http.request = function(...args) { + interceptRequest(args, applicationId) + + return originalHttpRequest.apply(this, args) + } + + http.get = function(...args) { + interceptRequest(args, applicationId) + + return originalHttpGet.apply(this, args) + } + + https.request = function(...args) { + interceptRequest(args, applicationId) + + return originalHttpsRequest.apply(this, args) + } + + https.get = function(...args) { + interceptRequest(args, applicationId) + + return originalHttpsGet.apply(this, args) + } + + // Intercept fetch API (Node.js 18+) + if (typeof global.fetch === 'function') { + const originalFetch = global.fetch + + global.fetch = function(resource, options = {}) { + // Ensure headers exist + if (!options.headers) { + options.headers = {} + } + + // Handle Headers object or plain object + if (typeof Headers !== 'undefined' && options.headers instanceof Headers) { + options.headers.set(ANALYTICS_HEADER_KEY, applicationId) + } else if (typeof options.headers === 'object') { + options.headers[ANALYTICS_HEADER_KEY] = applicationId + } + + return originalFetch.call(this, resource, options) + } + } + } + const appUid = applicationId.replace(/-/g, '').toLowerCase() try { @@ -33,16 +93,43 @@ exports.apply = applicationId => { applied = true } +function interceptRequest(args, applicationId) { + // Handle different argument formats for http.request/get + let options = args[0] + + if (typeof options === 'string' || options instanceof URL) { + // Convert URL/string to options object + options = typeof options === 'string' ? new URL(options) : options + args[0] = { + protocol: options.protocol, + hostname: options.hostname, + port : options.port, + path : options.pathname + options.search, + headers : {} + } + } else if (options && typeof options === 'object') { + // Ensure headers object exists + if (!options.headers) { + options.headers = {} + } + } + + // Add custom header to all requests + if (args[0] && args[0].headers) { + args[0].headers[ANALYTICS_HEADER_KEY] = applicationId + } +} + function overrideModuleMethods(name, module) { Object.keys(module).forEach(method => { if (typeof module[method] === 'function') { - module[method] = getThrower(`Calling ${name}.${method} method`) + module[method] = getThrower(`Calling ${ name }.${ method } method`) } }) } function getThrower(initiator) { return function() { - throw new Error(`${initiator} is not allowed inside Business Logic`) + throw new Error(`${ initiator } is not allowed inside Business Logic`) } } \ No newline at end of file