@oneuptime/common 12.0.11 → 12.0.13

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 (251) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
  27. package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
  28. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  29. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  30. package/Server/Utils/LogRedaction.ts +576 -0
  31. package/Server/Utils/Logger.ts +123 -46
  32. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  33. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
  34. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  35. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  36. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  37. package/Server/Utils/StartServer.ts +30 -9
  38. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  39. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  40. package/Server/Utils/VM/VMRunner.ts +693 -874
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  42. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  43. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  44. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  45. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  46. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  47. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  48. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  49. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  50. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  51. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  52. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  53. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  54. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  55. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  56. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  57. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  58. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  59. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  60. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  61. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  62. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  63. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  64. package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
  65. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  66. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  67. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  68. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  69. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
  71. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  72. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  73. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  74. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  75. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  76. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  77. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  78. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  79. package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
  80. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
  81. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  84. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  85. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  86. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  87. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  88. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  90. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  91. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  92. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  93. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  94. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  95. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  96. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  97. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  98. package/Types/API/Hostname.ts +79 -0
  99. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  100. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  101. package/Types/Database/DatabaseProperty.ts +38 -0
  102. package/Types/Database/TableColumn.ts +34 -7
  103. package/Types/Events/Recurring.ts +16 -1
  104. package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
  105. package/Types/Monitor/MonitorStep.ts +13 -1
  106. package/Types/Monitor/MonitorType.ts +345 -40
  107. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  108. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  109. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  110. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  111. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  112. package/UI/Components/Footer/Footer.tsx +32 -6
  113. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  114. package/UI/Components/Forms/Types/Field.ts +8 -0
  115. package/UI/Components/Loader/PageLoader.tsx +29 -8
  116. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  117. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  118. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  119. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  120. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  121. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  122. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  123. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  124. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  125. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  127. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  129. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  131. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  133. package/build/dist/Server/API/AIChatAPI.js +48 -35
  134. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  135. package/build/dist/Server/API/CommonAPI.js +59 -0
  136. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  137. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  138. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  139. package/build/dist/Server/API/SlackAPI.js +13 -10
  140. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  141. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  142. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  143. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  144. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  151. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  152. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  153. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  154. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  155. package/build/dist/Server/Services/DatabaseService.js +15 -6
  156. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  157. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  158. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  159. package/build/dist/Server/Services/LogService.js +16 -0
  160. package/build/dist/Server/Services/LogService.js.map +1 -1
  161. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  162. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  163. package/build/dist/Server/Services/SpanService.js +16 -0
  164. package/build/dist/Server/Services/SpanService.js.map +1 -1
  165. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  166. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  167. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  168. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  169. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  170. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  171. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  172. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  173. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
  174. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  175. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
  176. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  177. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  178. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  179. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  180. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  181. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  182. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  183. package/build/dist/Server/Utils/Logger.js +98 -45
  184. package/build/dist/Server/Utils/Logger.js.map +1 -1
  185. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  186. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  187. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  189. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  190. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  192. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  193. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  194. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +10 -7
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  198. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  199. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  200. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  201. package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
  202. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  203. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  204. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  205. package/build/dist/Types/API/Hostname.js +63 -0
  206. package/build/dist/Types/API/Hostname.js.map +1 -1
  207. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  208. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  209. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  210. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  211. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  212. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  213. package/build/dist/Types/Database/TableColumn.js +24 -6
  214. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  215. package/build/dist/Types/Events/Recurring.js +13 -1
  216. package/build/dist/Types/Events/Recurring.js.map +1 -1
  217. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  218. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  219. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  220. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  221. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  222. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  223. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  224. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  225. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  226. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  227. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  228. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  229. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  230. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  231. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  232. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  233. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  234. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  235. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  236. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  237. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  238. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  239. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  240. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  241. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  242. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  243. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  244. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  245. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  246. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  247. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  248. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  249. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  250. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  251. package/package.json +1 -1
@@ -13,272 +13,7 @@ import http from "http";
13
13
  import https from "https";
14
14
  import ivm from "isolated-vm";
15
15
  import CaptureSpan from "../Telemetry/CaptureSpan";
16
- import vm from "vm";
17
16
  import SSRFProtection from "../SSRFProtection";
18
- /**
19
- * Symbol used to retrieve the real (unwrapped) target from a sandbox proxy.
20
- * Hidden from user code via ownKeys / has traps.
21
- */
22
- const PROXY_TARGET_SYMBOL = Symbol("sandboxProxyTarget");
23
- /**
24
- * Hardening prelude injected before user code in `runCodeInNodeVM`.
25
- *
26
- * Node's `vm` module is not a security boundary. The published PoC for
27
- * GHSA-g9cp-35m2-fjv6 forces a stack-overflow `RangeError`, walks
28
- * `e.__proto__.__proto__.__proto__` to `Object.prototype`, then reads
29
- * `.toString.constructor` to obtain a `Function` constructor that compiles
30
- * code in a realm where `process.binding('spawn_sync')` is reachable.
31
- *
32
- * This prelude closes that path by:
33
- * - severing `Error.prototype`'s link to `Object.prototype` so the 3-level
34
- * walk lands on `null` instead of `Object.prototype`;
35
- * - deleting `.constructor` from every built-in prototype, so even a
36
- * different walk (e.g. `(0).constructor.constructor`) cannot resolve to a
37
- * function constructor;
38
- * - clearing `Function` / `eval` from the sandbox global;
39
- * - freezing the affected prototypes so user code cannot reattach them.
40
- *
41
- * This is a hotfix for the public PoC. The durable fix is to drop
42
- * `runCodeInNodeVM` in favor of running synthetic monitor scripts in an
43
- * out-of-process sandbox (tracked on the `probe-runner` branch).
44
- */
45
- const VM_HARDENING_PRELUDE = `(() => {
46
- const _ctors = [
47
- Object, Function, Array, String, Number, Boolean, RegExp,
48
- Error, RangeError, TypeError, SyntaxError, ReferenceError, EvalError, URIError,
49
- Symbol, Date, Map, Set, WeakMap, WeakSet, Promise, Proxy,
50
- ArrayBuffer, DataView,
51
- Int8Array, Uint8Array, Uint8ClampedArray,
52
- Int16Array, Uint16Array, Int32Array, Uint32Array,
53
- Float32Array, Float64Array,
54
- ];
55
- if (typeof BigInt !== 'undefined') _ctors.push(BigInt);
56
-
57
- for (const C of _ctors) {
58
- try { if (C && C.prototype) delete C.prototype.constructor; } catch (_) {}
59
- }
60
-
61
- // Generator / async-function prototypes have no named global — reach via syntax.
62
- try { delete Object.getPrototypeOf(function*(){}).constructor; } catch (_) {}
63
- try { delete Object.getPrototypeOf(async function(){}).constructor; } catch (_) {}
64
- try { delete Object.getPrototypeOf(async function*(){}).constructor; } catch (_) {}
65
-
66
- try { Object.setPrototypeOf(Error.prototype, null); } catch (_) {}
67
-
68
- try {
69
- Object.defineProperty(globalThis, 'Function', {
70
- value: undefined, writable: false, configurable: false,
71
- });
72
- } catch (_) {}
73
- try {
74
- Object.defineProperty(globalThis, 'eval', {
75
- value: undefined, writable: false, configurable: false,
76
- });
77
- } catch (_) {}
78
-
79
- for (const C of _ctors) {
80
- try { if (C && C.prototype) Object.freeze(C.prototype); } catch (_) {}
81
- }
82
- })();`;
83
- /** Properties blocked on every host-realm object exposed to the sandbox. */
84
- const BLOCKED_SANDBOX_PROPERTIES = new Set([
85
- "constructor",
86
- "__proto__",
87
- "prototype",
88
- "mainModule",
89
- /*
90
- * Block Playwright methods that can spawn processes or access internals.
91
- * Prevents RCE via browser.browserType().launch({executablePath:"/bin/sh"})
92
- * and traversal via page.context().browser().browserType().launch(...)
93
- */
94
- "browserType", // Browser → BrowserType (which has launch/connect)
95
- "_browserType", // Internal alias for browserType — same escape vector
96
- "launch", // BrowserType.launch() spawns a child process
97
- "launchServer", // BrowserType.launchServer() spawns a browser server process
98
- "launchPersistentContext", // BrowserType.launchPersistentContext() spawns a child process
99
- "connectOverCDP", // BrowserType.connectOverCDP() connects via Chrome DevTools Protocol
100
- "connect", // BrowserType.connect() connects to a remote browser
101
- "newCDPSession", // BrowserContext/Page.newCDPSession() opens raw CDP sessions
102
- ]);
103
- /**
104
- * Wraps a host-realm value in a Proxy that blocks prototype-chain traversal.
105
- * Primitives and null/undefined pass through unchanged.
106
- * Object proxies are cached to preserve identity; function proxies are created
107
- * per-access so they bind to the correct `this` (parent object).
108
- */
109
- function createSandboxProxy(value, cache, parentObj) {
110
- if (value === null || value === undefined) {
111
- return value;
112
- }
113
- const valueType = typeof value;
114
- if (valueType !== "object" && valueType !== "function") {
115
- return value;
116
- }
117
- const target = value;
118
- if (valueType === "function") {
119
- /*
120
- * Function proxies are NOT cached because the same function may be a method
121
- * on different parent objects and needs a different `this` binding each time.
122
- */
123
- const fnProxy = new Proxy(target, {
124
- get(fnTarget, prop) {
125
- if (prop === PROXY_TARGET_SYMBOL) {
126
- return fnTarget;
127
- }
128
- if (typeof prop === "string" &&
129
- BLOCKED_SANDBOX_PROPERTIES.has(prop)) {
130
- return undefined;
131
- }
132
- const val = Reflect.get(fnTarget, prop, fnTarget);
133
- return createSandboxProxy(val, cache, fnTarget);
134
- },
135
- getPrototypeOf() {
136
- return null;
137
- },
138
- apply(fnTarget, _thisArg, args) {
139
- const thisObj = (parentObj ||
140
- fnTarget);
141
- try {
142
- const result = Reflect.apply(fnTarget, thisObj, args);
143
- if (result instanceof Promise) {
144
- return result.then((v) => {
145
- return createSandboxProxy(v, cache);
146
- }, (err) => {
147
- throw createSandboxProxy(err, cache);
148
- });
149
- }
150
- return createSandboxProxy(result, cache);
151
- }
152
- catch (err) {
153
- throw createSandboxProxy(err, cache);
154
- }
155
- },
156
- has(fnTarget, prop) {
157
- if (typeof prop === "string" &&
158
- BLOCKED_SANDBOX_PROPERTIES.has(prop)) {
159
- return false;
160
- }
161
- return Reflect.has(fnTarget, prop);
162
- },
163
- ownKeys(fnTarget) {
164
- return Reflect.ownKeys(fnTarget).filter((k) => {
165
- return !(typeof k === "string" && BLOCKED_SANDBOX_PROPERTIES.has(k));
166
- });
167
- },
168
- getOwnPropertyDescriptor(fnTarget, prop) {
169
- if (typeof prop === "string" &&
170
- BLOCKED_SANDBOX_PROPERTIES.has(prop)) {
171
- return undefined;
172
- }
173
- const desc = Reflect.getOwnPropertyDescriptor(fnTarget, prop);
174
- if (desc && "value" in desc) {
175
- desc.value = createSandboxProxy(desc.value, cache, fnTarget);
176
- }
177
- return desc;
178
- },
179
- });
180
- return fnProxy;
181
- }
182
- // Object — use cache to preserve identity and handle circular references
183
- if (cache.has(target)) {
184
- return cache.get(target);
185
- }
186
- const objProxy = new Proxy(target, {
187
- get(objTarget, prop) {
188
- if (prop === PROXY_TARGET_SYMBOL) {
189
- return objTarget;
190
- }
191
- if (typeof prop === "string" && BLOCKED_SANDBOX_PROPERTIES.has(prop)) {
192
- return undefined;
193
- }
194
- const val = Reflect.get(objTarget, prop, objTarget);
195
- return createSandboxProxy(val, cache, objTarget);
196
- },
197
- getPrototypeOf() {
198
- return null;
199
- },
200
- set(objTarget, prop, newValue) {
201
- return Reflect.set(objTarget, prop, newValue);
202
- },
203
- has(objTarget, prop) {
204
- if (typeof prop === "string" && BLOCKED_SANDBOX_PROPERTIES.has(prop)) {
205
- return false;
206
- }
207
- return Reflect.has(objTarget, prop);
208
- },
209
- ownKeys(objTarget) {
210
- return Reflect.ownKeys(objTarget).filter((k) => {
211
- return !(typeof k === "string" && BLOCKED_SANDBOX_PROPERTIES.has(k));
212
- });
213
- },
214
- getOwnPropertyDescriptor(objTarget, prop) {
215
- if (typeof prop === "string" && BLOCKED_SANDBOX_PROPERTIES.has(prop)) {
216
- return undefined;
217
- }
218
- const desc = Reflect.getOwnPropertyDescriptor(objTarget, prop);
219
- if (desc && "value" in desc) {
220
- desc.value = createSandboxProxy(desc.value, cache, objTarget);
221
- }
222
- return desc;
223
- },
224
- });
225
- cache.set(target, objProxy);
226
- return objProxy;
227
- }
228
- /**
229
- * Recursively unwraps sandbox proxies in a return value so the host code
230
- * receives original objects (e.g. Buffers that pass `instanceof` checks).
231
- */
232
- export function deepUnwrapProxies(value, visited) {
233
- if (value === null || value === undefined) {
234
- return value;
235
- }
236
- const valueType = typeof value;
237
- if (valueType !== "object" && valueType !== "function") {
238
- return value;
239
- }
240
- const obj = value;
241
- // If it's one of our proxies, unwrap to the original target
242
- try {
243
- const underlying = obj[PROXY_TARGET_SYMBOL];
244
- if (underlying !== undefined) {
245
- return underlying;
246
- }
247
- }
248
- catch (_a) {
249
- // Not a proxy or symbol access failed — treat as a plain value
250
- }
251
- if (!visited) {
252
- visited = new WeakSet();
253
- }
254
- if (visited.has(obj)) {
255
- return obj;
256
- }
257
- visited.add(obj);
258
- if (Array.isArray(obj)) {
259
- for (let i = 0; i < obj.length; i++) {
260
- obj[i] = deepUnwrapProxies(obj[i], visited);
261
- }
262
- }
263
- else if (valueType === "object") {
264
- for (const key of Object.keys(obj)) {
265
- obj[key] = deepUnwrapProxies(obj[key], visited);
266
- }
267
- }
268
- return obj;
269
- }
270
- /**
271
- * Unwraps a single value if it is a sandbox proxy, otherwise returns it as-is.
272
- */
273
- function unwrapProxy(value) {
274
- if (value && typeof value === "object") {
275
- const underlying = value[PROXY_TARGET_SYMBOL];
276
- if (underlying !== undefined) {
277
- return underlying;
278
- }
279
- }
280
- return value;
281
- }
282
17
  export default class VMRunner {
283
18
  /*
284
19
  * Works out which URL the sandbox's axios call will actually dial, so the
@@ -313,188 +48,123 @@ export default class VMRunner {
313
48
  }
314
49
  return absolute;
315
50
  }
316
- static async runCodeInNodeVM(data) {
51
+ static async runCodeInSandbox(data) {
317
52
  const { code, options } = data;
318
53
  const timeout = options.timeout || 5000;
319
54
  const logMessages = [];
320
- const MAX_LOG_BYTES = 1000000; // 1MB cap
321
- let totalLogBytes = 0;
322
55
  const capturedMetrics = [];
323
56
  const MAX_METRICS = 100;
324
- const pendingTimeouts = [];
325
- const pendingIntervals = [];
326
- const wrappedSetTimeout = (fn, ms, ...rest) => {
327
- const handle = setTimeout(fn, ms, ...rest);
328
- pendingTimeouts.push(handle);
329
- return handle;
330
- };
331
- const wrappedClearTimeout = (handle) => {
332
- const actual = unwrapProxy(handle);
333
- clearTimeout(actual);
334
- const idx = pendingTimeouts.indexOf(actual);
335
- if (idx !== -1) {
336
- pendingTimeouts.splice(idx, 1);
337
- }
338
- };
339
- const wrappedSetInterval = (fn, ms, ...rest) => {
340
- const handle = setInterval(fn, ms, ...rest);
341
- pendingIntervals.push(handle);
342
- return handle;
343
- };
344
- const wrappedClearInterval = (handle) => {
345
- const actual = unwrapProxy(handle);
346
- clearInterval(actual);
347
- const idx = pendingIntervals.indexOf(actual);
348
- if (idx !== -1) {
349
- pendingIntervals.splice(idx, 1);
350
- }
351
- };
352
- // Proxy cache shared across all wrapped host objects in this execution
353
- const proxyCache = new WeakMap();
354
- // Use null-prototype object to break this.constructor chain on the global
355
- const sandbox = Object.create(null);
356
- sandbox["process"] = Object.freeze(Object.create(null));
357
- sandbox["console"] = createSandboxProxy({
358
- log: (...args) => {
359
- const msg = args.join(" ");
360
- totalLogBytes += msg.length;
361
- if (totalLogBytes <= MAX_LOG_BYTES) {
362
- logMessages.push(msg);
363
- }
364
- },
365
- }, proxyCache);
366
- sandbox["http"] = createSandboxProxy(http, proxyCache);
367
- sandbox["https"] = createSandboxProxy(https, proxyCache);
368
- sandbox["axios"] = createSandboxProxy(axios, proxyCache);
369
- sandbox["crypto"] = createSandboxProxy(crypto, proxyCache);
370
- sandbox["setTimeout"] = createSandboxProxy(wrappedSetTimeout, proxyCache);
371
- sandbox["clearTimeout"] = createSandboxProxy(wrappedClearTimeout, proxyCache);
372
- sandbox["setInterval"] = createSandboxProxy(wrappedSetInterval, proxyCache);
373
- sandbox["clearInterval"] = createSandboxProxy(wrappedClearInterval, proxyCache);
374
- sandbox["oneuptime"] = createSandboxProxy({
375
- captureMetric: (name, value, attributes) => {
376
- if (typeof name !== "string" || name.length === 0) {
377
- return;
378
- }
379
- if (typeof value !== "number" || isNaN(value)) {
380
- return;
381
- }
382
- if (capturedMetrics.length >= MAX_METRICS) {
383
- return;
57
+ const MAX_LOG_MESSAGES = 1000;
58
+ const MAX_LOG_BYTES = 1000000;
59
+ const MAX_SCRIPT_ERROR_MESSAGE_LENGTH = 10000;
60
+ let logBytes = 0;
61
+ const pendingHostTimeouts = new Set();
62
+ const pendingAxiosControllers = new Set();
63
+ let acceptingHostOperations = true;
64
+ const pendingAxiosOperations = new Map();
65
+ let nextAxiosOperationId = 0;
66
+ const pendingSleepOperations = new Map();
67
+ let nextSleepOperationId = 0;
68
+ const sanitizeScriptError = (error) => {
69
+ let message = "Sandbox script failed";
70
+ try {
71
+ if (typeof error === "string") {
72
+ message = error;
384
73
  }
385
- const metric = {
386
- name: name.substring(0, 200),
387
- value: value,
388
- };
389
- if (attributes && typeof attributes === "object") {
390
- const safeAttrs = {};
391
- for (const [k, v] of Object.entries(attributes)) {
392
- if (typeof v === "string" ||
393
- typeof v === "number" ||
394
- typeof v === "boolean") {
395
- safeAttrs[k] = String(v);
396
- }
74
+ else if (error && typeof error === "object") {
75
+ const candidateMessage = error["message"];
76
+ if (typeof candidateMessage === "string") {
77
+ message = candidateMessage;
397
78
  }
398
- metric.attributes = safeAttrs;
399
79
  }
400
- capturedMetrics.push(metric);
401
- },
402
- }, proxyCache);
403
- // Wrap any additional context (e.g. Playwright browser/page objects)
404
- if (options.context) {
405
- for (const key of Object.keys(options.context)) {
406
- const val = options.context[key];
407
- sandbox[key] =
408
- typeof val === "string" ? val : createSandboxProxy(val, proxyCache);
409
80
  }
410
- }
411
- if (options.args) {
412
- // args is plain JSON data — no host functions to protect against
413
- sandbox["args"] = options.args;
414
- }
415
- vm.createContext(sandbox, {
416
- codeGeneration: {
417
- strings: false,
418
- wasm: false,
419
- },
420
- });
421
- const script = `(async()=>{
422
- ${VM_HARDENING_PRELUDE}
423
- ${code}
424
- })()`;
425
- try {
426
- let returnVal;
427
- let scriptError;
428
- try {
429
- /*
430
- * vm timeout only covers synchronous CPU time, so wrap with
431
- * Promise.race to also cover async operations (network, timers, etc.)
432
- */
433
- const vmPromise = vm.runInContext(script, sandbox, {
434
- timeout: timeout,
435
- });
436
- const overallTimeout = new Promise((_resolve, reject) => {
437
- const handle = global.setTimeout(() => {
438
- reject(new Error("Script execution timed out"));
439
- }, timeout + 5000);
440
- // Don't let this timer keep the process alive
441
- handle.unref();
442
- });
443
- returnVal = await Promise.race([vmPromise, overallTimeout]);
81
+ catch (_a) {
82
+ // Do not invoke any attacker-controlled coercion while reporting errors.
444
83
  }
445
- catch (err) {
446
- /*
447
- * Capture user-thrown errors (including timeouts) so the caller can
448
- * still access side-channel data collected before the throw — e.g.
449
- * screenshots assigned to a host-realm object passed via `context`.
450
- * Rethrowing here would discard those partial results.
451
- */
452
- scriptError =
453
- err instanceof Error
454
- ? err
455
- : new Error(typeof err === "string" ? err : String(err));
456
- }
457
- return {
458
- returnValue: deepUnwrapProxies(returnVal),
459
- logMessages,
460
- capturedMetrics,
461
- scriptError,
462
- };
463
- }
464
- finally {
465
- // Clean up any lingering timers to prevent resource leaks
466
- for (const handle of pendingTimeouts) {
467
- clearTimeout(handle);
84
+ let sanitizedMessage = "";
85
+ for (const character of message) {
86
+ const characterCode = character.charCodeAt(0);
87
+ if (characterCode === 9 ||
88
+ characterCode === 10 ||
89
+ characterCode === 13 ||
90
+ (characterCode >= 32 && characterCode !== 127)) {
91
+ sanitizedMessage += character;
92
+ }
93
+ if (sanitizedMessage.length >= MAX_SCRIPT_ERROR_MESSAGE_LENGTH) {
94
+ break;
95
+ }
468
96
  }
469
- for (const handle of pendingIntervals) {
470
- clearInterval(handle);
97
+ message = sanitizedMessage.substring(0, MAX_SCRIPT_ERROR_MESSAGE_LENGTH);
98
+ if (!message) {
99
+ message = "Sandbox script failed";
471
100
  }
472
- }
473
- }
474
- static async runCodeInSandbox(data) {
475
- const { code, options } = data;
476
- const timeout = options.timeout || 5000;
477
- const logMessages = [];
478
- const capturedMetrics = [];
479
- const MAX_METRICS = 100;
101
+ /*
102
+ * Deliberately create a fresh host Error so isolate-owned properties and
103
+ * stack frames never escape with the result.
104
+ */
105
+ return new Error(message);
106
+ };
480
107
  const isolate = new ivm.Isolate({ memoryLimit: 128 });
481
108
  try {
482
109
  const context = await isolate.createContext();
483
110
  const jail = context.global;
484
111
  // Set up global object
485
112
  await jail.set("global", jail.derefInto());
486
- // console.log - fire-and-forget callback
487
- await jail.set("_log", new ivm.Callback((...args) => {
488
- logMessages.push(args.join(" "));
489
- }));
113
+ /*
114
+ * Callback values become ordinary functions in the destination isolate.
115
+ * Never expose ivm.Reference or ivm.ExternalCopy handles to user code:
116
+ * their prototype methods can be used to cross the isolate boundary.
117
+ */
118
+ await jail.set("__oneuptimeHostLogCallback", new ivm.Callback((message) => {
119
+ if (logMessages.length >= MAX_LOG_MESSAGES) {
120
+ return;
121
+ }
122
+ const messageBytes = Buffer.byteLength(message, "utf8");
123
+ if (logBytes + messageBytes > MAX_LOG_BYTES) {
124
+ return;
125
+ }
126
+ logBytes += messageBytes;
127
+ logMessages.push(message);
128
+ }, { sync: true }));
490
129
  await context.eval(`
491
- const console = { log: (...a) => _log(...a.map(v => {
492
- try { return typeof v === 'object' ? JSON.stringify(v) : String(v); }
493
- catch(_) { return String(v); }
494
- }))};
130
+ (() => {
131
+ const hostLog = globalThis.__oneuptimeHostLogCallback;
132
+ delete globalThis.__oneuptimeHostLogCallback;
133
+ let sandboxLogCount = 0;
134
+ let sandboxLogCharacters = 0;
135
+
136
+ const sandboxConsole = Object.freeze({
137
+ log: (...args) => {
138
+ if (sandboxLogCount >= 1000 || sandboxLogCharacters >= 500000) {
139
+ return;
140
+ }
141
+
142
+ const message = args.map(value => {
143
+ try {
144
+ return typeof value === 'object' ? JSON.stringify(value) : String(value);
145
+ } catch (_) {
146
+ return String(value);
147
+ }
148
+ }).join(' ').substring(0, 250000);
149
+
150
+ if (sandboxLogCharacters + message.length > 500000) {
151
+ return;
152
+ }
153
+
154
+ sandboxLogCount += 1;
155
+ sandboxLogCharacters += message.length;
156
+ hostLog(message);
157
+ }
158
+ });
159
+
160
+ Object.defineProperty(globalThis, 'console', {
161
+ value: sandboxConsole,
162
+ writable: false,
163
+ configurable: false,
164
+ });
165
+ })();
495
166
  `);
496
- // oneuptime.captureMetric - fire-and-forget callback
497
- await jail.set("_captureMetric", new ivm.Callback((name, value, attributesJson) => {
167
+ await jail.set("__oneuptimeHostMetricCallback", new ivm.Callback((name, value, attributesJson) => {
498
168
  if (capturedMetrics.length >= MAX_METRICS) {
499
169
  return;
500
170
  }
@@ -515,25 +185,43 @@ export default class VMRunner {
515
185
  }
516
186
  }
517
187
  capturedMetrics.push(metric);
518
- }));
188
+ }, { sync: true }));
519
189
  await context.eval(`
520
- const oneuptime = {
521
- captureMetric: (name, value, attributes) => {
522
- if (typeof name !== 'string' || name.length === 0) return;
523
- if (typeof value !== 'number' || isNaN(value)) return;
524
- const attrJson = attributes ? JSON.stringify(attributes) : undefined;
525
- _captureMetric(String(name), String(value), attrJson);
526
- }
527
- };
190
+ (() => {
191
+ const hostCaptureMetric = globalThis.__oneuptimeHostMetricCallback;
192
+ delete globalThis.__oneuptimeHostMetricCallback;
193
+
194
+ const sandboxOneUptime = Object.freeze({
195
+ captureMetric: (name, value, attributes) => {
196
+ if (typeof name !== 'string' || name.length === 0) return;
197
+ if (typeof value !== 'number' || isNaN(value)) return;
198
+ const attrJson = attributes ? JSON.stringify(attributes) : undefined;
199
+ hostCaptureMetric(String(name), String(value), attrJson);
200
+ }
201
+ });
202
+
203
+ Object.defineProperty(globalThis, 'oneuptime', {
204
+ value: sandboxOneUptime,
205
+ writable: false,
206
+ configurable: false,
207
+ });
208
+ })();
528
209
  `);
529
210
  // args - deep copy into isolate
530
- if (options.args) {
531
- await jail.set("_args", new ivm.ExternalCopy(options.args).copyInto());
532
- await context.eval("const args = _args;");
533
- }
534
- else {
535
- await context.eval("const args = {};");
536
- }
211
+ await jail.set("__oneuptimeCopiedArgs", options.args || {}, {
212
+ copy: true,
213
+ });
214
+ await context.eval(`
215
+ (() => {
216
+ const copiedArgs = globalThis.__oneuptimeCopiedArgs;
217
+ delete globalThis.__oneuptimeCopiedArgs;
218
+ Object.defineProperty(globalThis, 'args', {
219
+ value: copiedArgs,
220
+ writable: false,
221
+ configurable: false,
222
+ });
223
+ })();
224
+ `);
537
225
  /*
538
226
  * http / https - provide Agent constructors that serialize across the boundary.
539
227
  * The sandbox Agent is a plain object with a marker; the host-side axios bridge
@@ -559,13 +247,13 @@ export default class VMRunner {
559
247
  `);
560
248
  /*
561
249
  * axios (get, head, options, post, put, patch, delete, request)
562
- * bridged via applySyncPromise.
250
+ * bridged through a copied async callback.
563
251
  *
564
252
  * For GET/HEAD/OPTIONS/DELETE: args = [method, url, configJson?]
565
253
  * For POST/PUT/PATCH: args = [method, url, bodyJson?, configJson?]
566
254
  * For REQUEST: args = ['request', '', configJson]
567
255
  */
568
- const axiosRef = new ivm.Reference(async (method, url, arg1, arg2) => {
256
+ const executeAxiosRequest = async (signal, method, url, arg1, arg2) => {
569
257
  const methodsWithBody = ["post", "put", "patch"];
570
258
  const hasBody = methodsWithBody.includes(method);
571
259
  /*
@@ -640,6 +328,11 @@ export default class VMRunner {
640
328
  delete safeConfig["transport"];
641
329
  delete safeConfig["adapter"];
642
330
  safeConfig["maxRedirects"] = 0;
331
+ Object.defineProperty(safeConfig, "signal", {
332
+ value: signal,
333
+ enumerable: true,
334
+ configurable: true,
335
+ });
643
336
  config = safeConfig;
644
337
  try {
645
338
  let response;
@@ -702,10 +395,86 @@ export default class VMRunner {
702
395
  }
703
396
  throw err;
704
397
  }
705
- });
706
- await jail.set("_axiosRef", axiosRef);
398
+ };
399
+ const axiosStartCallback = new ivm.Callback((method, url, arg1, arg2) => {
400
+ const operationId = String(++nextAxiosOperationId);
401
+ if (!acceptingHostOperations) {
402
+ return operationId;
403
+ }
404
+ if (pendingAxiosOperations.size >= 100) {
405
+ pendingAxiosOperations.set(operationId, {
406
+ status: "rejected",
407
+ errorMessage: "Too many pending HTTP requests",
408
+ });
409
+ return operationId;
410
+ }
411
+ pendingAxiosOperations.set(operationId, { status: "pending" });
412
+ const abortController = new AbortController();
413
+ pendingAxiosControllers.add(abortController);
414
+ void executeAxiosRequest(abortController.signal, method, url, arg1, arg2)
415
+ .then((value) => {
416
+ if (pendingAxiosOperations.has(operationId)) {
417
+ pendingAxiosOperations.set(operationId, {
418
+ status: "fulfilled",
419
+ value,
420
+ });
421
+ }
422
+ }, (error) => {
423
+ if (pendingAxiosOperations.has(operationId)) {
424
+ pendingAxiosOperations.set(operationId, {
425
+ status: "rejected",
426
+ errorMessage: sanitizeScriptError(error).message,
427
+ });
428
+ }
429
+ })
430
+ .finally(() => {
431
+ pendingAxiosControllers.delete(abortController);
432
+ });
433
+ return operationId;
434
+ }, { async: true });
435
+ const axiosPollCallback = new ivm.Callback((operationId) => {
436
+ const operation = pendingAxiosOperations.get(operationId);
437
+ if (!operation) {
438
+ return JSON.stringify({
439
+ status: "rejected",
440
+ errorMessage: "HTTP request result is unavailable",
441
+ });
442
+ }
443
+ if (operation.status !== "pending") {
444
+ pendingAxiosOperations.delete(operationId);
445
+ }
446
+ return JSON.stringify(operation);
447
+ }, { async: true });
448
+ await jail.set("__oneuptimeHostAxiosStartCallback", axiosStartCallback);
449
+ await jail.set("__oneuptimeHostAxiosPollCallback", axiosPollCallback);
707
450
  await context.eval(`
708
- function _assertNoFunctions(obj, path) {
451
+ (() => {
452
+ const hostAxiosStart = globalThis.__oneuptimeHostAxiosStartCallback;
453
+ const hostAxiosPoll = globalThis.__oneuptimeHostAxiosPollCallback;
454
+ delete globalThis.__oneuptimeHostAxiosStartCallback;
455
+ delete globalThis.__oneuptimeHostAxiosPollCallback;
456
+ const axiosPollWaitArray = new Int32Array(new SharedArrayBuffer(4));
457
+
458
+ async function hostAxios(method, url, arg1, arg2) {
459
+ const operationId = await hostAxiosStart(method, url, arg1, arg2);
460
+
461
+ while (true) {
462
+ const operation = JSON.parse(await hostAxiosPoll(operationId));
463
+
464
+ if (operation.status === 'pending') {
465
+ Atomics.wait(axiosPollWaitArray, 0, 0, 1);
466
+ continue;
467
+ }
468
+
469
+ if (operation.status === 'rejected') {
470
+ throw new Error(operation.errorMessage);
471
+ }
472
+
473
+ return operation.value;
474
+ }
475
+ }
476
+
477
+ function assertNoFunctions(obj, path) {
709
478
  if (!obj || typeof obj !== 'object') return;
710
479
  if (Array.isArray(obj)) {
711
480
  for (let i = 0; i < obj.length; i++) {
@@ -717,7 +486,7 @@ export default class VMRunner {
717
486
  );
718
487
  }
719
488
  if (obj[i] && typeof obj[i] === 'object') {
720
- _assertNoFunctions(obj[i], fullPath);
489
+ assertNoFunctions(obj[i], fullPath);
721
490
  }
722
491
  }
723
492
  return;
@@ -731,12 +500,12 @@ export default class VMRunner {
731
500
  );
732
501
  }
733
502
  if (obj[key] && typeof obj[key] === 'object') {
734
- _assertNoFunctions(obj[key], fullPath);
503
+ assertNoFunctions(obj[key], fullPath);
735
504
  }
736
505
  }
737
506
  }
738
507
 
739
- function _parseAxiosResult(r) {
508
+ function parseAxiosResult(r) {
740
509
  const parsed = JSON.parse(r);
741
510
  if (parsed && parsed.__isAxiosError) {
742
511
  const err = new Error(parsed.message);
@@ -753,7 +522,7 @@ export default class VMRunner {
753
522
  return parsed;
754
523
  }
755
524
 
756
- function _makeAxiosInstance(defaults) {
525
+ function makeAxiosInstance(defaults) {
757
526
  function mergeConfig(overrides) {
758
527
  if (!defaults && !overrides) return undefined;
759
528
  if (!defaults) return overrides;
@@ -767,9 +536,9 @@ export default class VMRunner {
767
536
 
768
537
  async function _request(config) {
769
538
  const merged = mergeConfig(config);
770
- if (merged) _assertNoFunctions(merged, 'config');
771
- const r = await _axiosRef.applySyncPromise(undefined, ['request', '', merged ? JSON.stringify(merged) : undefined]);
772
- return _parseAxiosResult(r);
539
+ if (merged) assertNoFunctions(merged, 'config');
540
+ const r = await hostAxios('request', '', merged ? JSON.stringify(merged) : undefined);
541
+ return parseAxiosResult(r);
773
542
  }
774
543
 
775
544
  // Make instance callable: axios(config) or axios(url, config)
@@ -783,62 +552,67 @@ export default class VMRunner {
783
552
  instance.request = _request;
784
553
  instance.get = async (url, config) => {
785
554
  const merged = mergeConfig(config);
786
- if (merged) _assertNoFunctions(merged, 'config');
787
- const r = await _axiosRef.applySyncPromise(undefined, ['get', url, merged ? JSON.stringify(merged) : undefined]);
788
- return _parseAxiosResult(r);
555
+ if (merged) assertNoFunctions(merged, 'config');
556
+ const r = await hostAxios('get', url, merged ? JSON.stringify(merged) : undefined);
557
+ return parseAxiosResult(r);
789
558
  };
790
559
  instance.head = async (url, config) => {
791
560
  const merged = mergeConfig(config);
792
- if (merged) _assertNoFunctions(merged, 'config');
793
- const r = await _axiosRef.applySyncPromise(undefined, ['head', url, merged ? JSON.stringify(merged) : undefined]);
794
- return _parseAxiosResult(r);
561
+ if (merged) assertNoFunctions(merged, 'config');
562
+ const r = await hostAxios('head', url, merged ? JSON.stringify(merged) : undefined);
563
+ return parseAxiosResult(r);
795
564
  };
796
565
  instance.options = async (url, config) => {
797
566
  const merged = mergeConfig(config);
798
- if (merged) _assertNoFunctions(merged, 'config');
799
- const r = await _axiosRef.applySyncPromise(undefined, ['options', url, merged ? JSON.stringify(merged) : undefined]);
800
- return _parseAxiosResult(r);
567
+ if (merged) assertNoFunctions(merged, 'config');
568
+ const r = await hostAxios('options', url, merged ? JSON.stringify(merged) : undefined);
569
+ return parseAxiosResult(r);
801
570
  };
802
571
  instance.post = async (url, data, config) => {
803
572
  const merged = mergeConfig(config);
804
- if (data) _assertNoFunctions(data, 'data');
805
- if (merged) _assertNoFunctions(merged, 'config');
806
- const r = await _axiosRef.applySyncPromise(undefined, ['post', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
807
- return _parseAxiosResult(r);
573
+ if (data) assertNoFunctions(data, 'data');
574
+ if (merged) assertNoFunctions(merged, 'config');
575
+ const r = await hostAxios('post', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined);
576
+ return parseAxiosResult(r);
808
577
  };
809
578
  instance.put = async (url, data, config) => {
810
579
  const merged = mergeConfig(config);
811
- if (data) _assertNoFunctions(data, 'data');
812
- if (merged) _assertNoFunctions(merged, 'config');
813
- const r = await _axiosRef.applySyncPromise(undefined, ['put', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
814
- return _parseAxiosResult(r);
580
+ if (data) assertNoFunctions(data, 'data');
581
+ if (merged) assertNoFunctions(merged, 'config');
582
+ const r = await hostAxios('put', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined);
583
+ return parseAxiosResult(r);
815
584
  };
816
585
  instance.patch = async (url, data, config) => {
817
586
  const merged = mergeConfig(config);
818
- if (data) _assertNoFunctions(data, 'data');
819
- if (merged) _assertNoFunctions(merged, 'config');
820
- const r = await _axiosRef.applySyncPromise(undefined, ['patch', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined]);
821
- return _parseAxiosResult(r);
587
+ if (data) assertNoFunctions(data, 'data');
588
+ if (merged) assertNoFunctions(merged, 'config');
589
+ const r = await hostAxios('patch', url, data ? JSON.stringify(data) : undefined, merged ? JSON.stringify(merged) : undefined);
590
+ return parseAxiosResult(r);
822
591
  };
823
592
  instance.delete = async (url, config) => {
824
593
  const merged = mergeConfig(config);
825
- if (merged) _assertNoFunctions(merged, 'config');
826
- const r = await _axiosRef.applySyncPromise(undefined, ['delete', url, merged ? JSON.stringify(merged) : undefined]);
827
- return _parseAxiosResult(r);
594
+ if (merged) assertNoFunctions(merged, 'config');
595
+ const r = await hostAxios('delete', url, merged ? JSON.stringify(merged) : undefined);
596
+ return parseAxiosResult(r);
828
597
  };
829
598
  instance.create = (instanceDefaults) => {
830
- if (instanceDefaults) _assertNoFunctions(instanceDefaults, 'defaults');
599
+ if (instanceDefaults) assertNoFunctions(instanceDefaults, 'defaults');
831
600
  const combinedDefaults = mergeConfig(instanceDefaults);
832
- return _makeAxiosInstance(combinedDefaults);
601
+ return makeAxiosInstance(combinedDefaults);
833
602
  };
834
603
 
835
604
  return instance;
836
605
  }
837
606
 
838
- const axios = _makeAxiosInstance(null);
607
+ Object.defineProperty(globalThis, 'axios', {
608
+ value: makeAxiosInstance(null),
609
+ writable: false,
610
+ configurable: false,
611
+ });
612
+ })();
839
613
  `);
840
- // crypto (createHash, createHmac, randomBytes, randomUUID, randomInt) - bridged via applySync
841
- const cryptoRef = new ivm.Reference((op, ...args) => {
614
+ // crypto (createHash, createHmac, randomBytes, randomUUID, randomInt)
615
+ const cryptoCallback = new ivm.Callback((op, ...args) => {
842
616
  switch (op) {
843
617
  case "createHash": {
844
618
  const [algorithm, inputData, encoding] = args;
@@ -868,47 +642,138 @@ export default class VMRunner {
868
642
  default:
869
643
  throw new Error(`Unsupported crypto operation: ${op}`);
870
644
  }
871
- });
872
- await jail.set("_cryptoRef", cryptoRef);
645
+ }, { sync: true });
646
+ await jail.set("__oneuptimeHostCryptoCallback", cryptoCallback);
873
647
  await context.eval(`
874
- const crypto = {
648
+ (() => {
649
+ const hostCrypto = globalThis.__oneuptimeHostCryptoCallback;
650
+ delete globalThis.__oneuptimeHostCryptoCallback;
651
+
652
+ const sandboxCrypto = {
875
653
  createHash: (algorithm) => ({
876
654
  _alg: algorithm, _data: '',
877
655
  update(d) { this._data = d; return this; },
878
- digest(enc) { return _cryptoRef.applySync(undefined, ['createHash', this._alg, this._data, enc || 'hex']); }
656
+ digest(enc) { return hostCrypto('createHash', this._alg, this._data, enc || 'hex'); }
879
657
  }),
880
658
  createHmac: (algorithm, key) => ({
881
659
  _alg: algorithm, _key: key, _data: '',
882
660
  update(d) { this._data = d; return this; },
883
- digest(enc) { return _cryptoRef.applySync(undefined, ['createHmac', this._alg, this._key, this._data, enc || 'hex']); }
661
+ digest(enc) { return hostCrypto('createHmac', this._alg, this._key, this._data, enc || 'hex'); }
884
662
  }),
885
663
  randomBytes: (size) => ({
886
- toString(enc) { return _cryptoRef.applySync(undefined, ['randomBytes', String(size)]); }
664
+ toString(enc) { return hostCrypto('randomBytes', String(size)); }
887
665
  }),
888
666
  randomUUID: () => {
889
- return _cryptoRef.applySync(undefined, ['randomUUID']);
667
+ return hostCrypto('randomUUID');
890
668
  },
891
669
  randomInt: (minOrMax, max) => {
892
670
  if (max === undefined) { max = minOrMax; minOrMax = 0; }
893
- return Number(_cryptoRef.applySync(undefined, ['randomInt', String(minOrMax), String(max)]));
671
+ return Number(hostCrypto('randomInt', String(minOrMax), String(max)));
894
672
  },
895
673
  };
674
+
675
+ Object.defineProperty(globalThis, 'crypto', {
676
+ value: sandboxCrypto,
677
+ writable: false,
678
+ configurable: false,
679
+ });
680
+ })();
896
681
  `);
897
- // setTimeout / sleep - bridged via applySyncPromise
898
- const sleepRef = new ivm.Reference((ms) => {
899
- return new Promise((resolve) => {
900
- global.setTimeout(resolve, Math.min(ms, timeout));
682
+ // setTimeout / sleep - bridged through copied start/poll callbacks
683
+ const sleepStartCallback = new ivm.Callback((ms) => {
684
+ const operationId = String(++nextSleepOperationId);
685
+ if (!acceptingHostOperations) {
686
+ return operationId;
687
+ }
688
+ const numericDelay = Number(ms);
689
+ const boundedDelay = Number.isFinite(numericDelay)
690
+ ? Math.max(0, Math.min(numericDelay, timeout))
691
+ : 0;
692
+ const timeoutHandle = global.setTimeout(() => {
693
+ const operation = pendingSleepOperations.get(operationId);
694
+ if (operation) {
695
+ operation.settled = true;
696
+ }
697
+ pendingHostTimeouts.delete(timeoutHandle);
698
+ }, boundedDelay);
699
+ pendingHostTimeouts.add(timeoutHandle);
700
+ pendingSleepOperations.set(operationId, {
701
+ settled: false,
901
702
  });
902
- });
903
- await jail.set("_sleepRef", sleepRef);
703
+ return operationId;
704
+ }, { async: true });
705
+ const sleepPollCallback = new ivm.Callback((operationId) => {
706
+ const operation = pendingSleepOperations.get(operationId);
707
+ if (!operation || operation.settled) {
708
+ pendingSleepOperations.delete(operationId);
709
+ return true;
710
+ }
711
+ return false;
712
+ }, { async: true });
713
+ await jail.set("__oneuptimeHostSleepStartCallback", sleepStartCallback);
714
+ await jail.set("__oneuptimeHostSleepPollCallback", sleepPollCallback);
904
715
  await context.eval(`
905
- function setTimeout(fn, ms) {
906
- _sleepRef.applySyncPromise(undefined, [ms || 0]);
907
- if (typeof fn === 'function') fn();
908
- }
909
- async function sleep(ms) {
910
- await _sleepRef.applySyncPromise(undefined, [ms || 0]);
911
- }
716
+ (() => {
717
+ const hostSleepStart = globalThis.__oneuptimeHostSleepStartCallback;
718
+ const hostSleepPoll = globalThis.__oneuptimeHostSleepPollCallback;
719
+ delete globalThis.__oneuptimeHostSleepStartCallback;
720
+ delete globalThis.__oneuptimeHostSleepPollCallback;
721
+ const activeTimers = new WeakSet();
722
+ const pollWaitArray = new Int32Array(new SharedArrayBuffer(4));
723
+
724
+ async function hostSleep(ms) {
725
+ const operationId = await hostSleepStart(ms || 0);
726
+
727
+ while (!(await hostSleepPoll(operationId))) {
728
+ // Pace polling on the isolate worker without blocking Node's
729
+ // event loop.
730
+ Atomics.wait(pollWaitArray, 0, 0, 1);
731
+ }
732
+ }
733
+
734
+ function sandboxSetTimeout(fn, ms, ...args) {
735
+ if (typeof fn !== 'function') {
736
+ throw new TypeError('setTimeout callback must be a function');
737
+ }
738
+
739
+ const handle = {};
740
+ activeTimers.add(handle);
741
+ hostSleep(ms || 0).then(() => {
742
+ if (activeTimers.delete(handle)) {
743
+ fn(...args);
744
+ }
745
+ });
746
+ return handle;
747
+ }
748
+
749
+ function sandboxClearTimeout(handle) {
750
+ if (handle && typeof handle === 'object') {
751
+ activeTimers.delete(handle);
752
+ }
753
+ }
754
+
755
+ async function sandboxSleep(ms) {
756
+ await hostSleep(ms || 0);
757
+ }
758
+
759
+ Object.defineProperties(globalThis, {
760
+ setTimeout: {
761
+ value: sandboxSetTimeout,
762
+ writable: false,
763
+ configurable: false,
764
+ },
765
+ clearTimeout: {
766
+ value: sandboxClearTimeout,
767
+ writable: false,
768
+ configurable: false,
769
+ },
770
+ sleep: {
771
+ value: sandboxSleep,
772
+ writable: false,
773
+ configurable: false,
774
+ },
775
+ });
776
+ })();
912
777
  `);
913
778
  /*
914
779
  * Wrap user code in async IIFE. JSON.stringify the return value inside
@@ -923,20 +788,26 @@ export default class VMRunner {
923
788
  try { return JSON.stringify(__result); }
924
789
  catch(_) { return undefined; }
925
790
  })()`;
926
- // Run with overall timeout covering both CPU and I/O wait
927
- const resultPromise = context.eval(wrappedCode, {
928
- promise: true,
929
- timeout: timeout,
930
- });
931
- const overallTimeout = new Promise((_resolve, reject) => {
932
- global.setTimeout(() => {
933
- reject(new Error("Script execution timed out"));
934
- }, timeout + 5000); // 5s grace period beyond isolate timeout
935
- });
936
- const result = await Promise.race([
937
- resultPromise,
938
- overallTimeout,
939
- ]);
791
+ let result;
792
+ let scriptError;
793
+ try {
794
+ // Run with overall timeout covering both CPU and I/O wait.
795
+ const resultPromise = context.eval(wrappedCode, {
796
+ promise: true,
797
+ timeout: timeout,
798
+ });
799
+ const overallTimeout = new Promise((_resolve, reject) => {
800
+ const timeoutHandle = global.setTimeout(() => {
801
+ pendingHostTimeouts.delete(timeoutHandle);
802
+ reject(new Error("Script execution timed out"));
803
+ }, timeout + 5000); // 5s grace period beyond isolate timeout
804
+ pendingHostTimeouts.add(timeoutHandle);
805
+ });
806
+ result = await Promise.race([resultPromise, overallTimeout]);
807
+ }
808
+ catch (error) {
809
+ scriptError = sanitizeScriptError(error);
810
+ }
940
811
  // Parse the JSON string returned from inside the isolate
941
812
  let returnValue;
942
813
  if (typeof result === "string") {
@@ -954,21 +825,27 @@ export default class VMRunner {
954
825
  returnValue,
955
826
  logMessages,
956
827
  capturedMetrics,
828
+ scriptError,
957
829
  };
958
830
  }
959
831
  finally {
832
+ acceptingHostOperations = false;
833
+ for (const timeoutHandle of pendingHostTimeouts) {
834
+ global.clearTimeout(timeoutHandle);
835
+ }
836
+ for (const abortController of pendingAxiosControllers) {
837
+ abortController.abort();
838
+ }
839
+ pendingHostTimeouts.clear();
840
+ pendingAxiosControllers.clear();
841
+ pendingSleepOperations.clear();
842
+ pendingAxiosOperations.clear();
960
843
  if (!isolate.isDisposed) {
961
844
  isolate.dispose();
962
845
  }
963
846
  }
964
847
  }
965
848
  }
966
- __decorate([
967
- CaptureSpan(),
968
- __metadata("design:type", Function),
969
- __metadata("design:paramtypes", [Object]),
970
- __metadata("design:returntype", Promise)
971
- ], VMRunner, "runCodeInNodeVM", null);
972
849
  __decorate([
973
850
  CaptureSpan(),
974
851
  __metadata("design:type", Function),