[APPS-2792] Add: reject Node built-in imports in backend files - #476
Draft
tyffical wants to merge 2 commits into
Draft
[APPS-2792] Add: reject Node built-in imports in backend files#476tyffical wants to merge 2 commits into
tyffical wants to merge 2 commits into
Conversation
Backend functions run in a restricted environment (isomorphic/fetch-based APIs only), so direct static imports of Node built-in modules (fs, child_process, net, etc.) in .backend.ts files are now rejected at build time in the Vite transform hook, right after AST parsing. This is a best-effort, defense-in-depth check on static import specifiers only — it does not catch require() or dynamic import() of a computed specifier.
Backend functions have no raw network access in every real production runtime -- Deno's --allow-net is off today, and the planned Terrapin-based v2 sandbox restricts it the same way -- so any outbound call must go through an Action Platform action ($.Actions or an @datadog/action-catalog typed wrapper), never a direct HTTP client. rejectNodeBuiltinImports only catches import specifiers; fetch and friends need no import at all, so this adds a separate, eslint-scope-based check for unshadowed references to fetch, XMLHttpRequest, WebSocket, and EventSource. Also corrects rejectNodeBuiltinImports' doc comment and error message, which previously pointed to fetch-based/isomorphic APIs as the allowed escape hatch -- no longer accurate now that fetch itself is blocked too.
tyffical
force-pushed
the
tiffany.trinh/apps-2792-sandboxing-import-restriction
branch
from
August 7, 2026 20:35
84b9e52 to
ec0f520
Compare
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
fetch); everything must go through an Action Platform action ($.Actionsor an@datadog/action-catalogtyped wrapper).fs,child_process,net, etc.) in.backend.tsfiles are rejected at build time, so an author gets immediate, actionable feedback instead of code that silently behaves differently (or breaks) once local Node execution lands.fetch,XMLHttpRequest,WebSocket,EventSource) need a separate check: they're bare globals, not imports, so import-specifier restriction can't catch them. This closes a real trap:fetchworks fine during local dev (nothing stopped it before this check existed) but fails once the app is published, since production's sandbox blocks it.fetchin the first place. That reduces how often this gets written at all, but only this build-time check guarantees it never ships, regardless of whether the code came from an AI, a human, or a copy-pasted snippet. Both layers exist for a reason — this PR isn't superseded by that guidance work.$that omit Node-specific types) is deferred — see Out of Scope below.Changes
rejectNodeBuiltinImports, which walks a.backend.tsfile's staticImportDeclarations and throws if any source is a Node built-in (vianode:prefix or Node's ownbuiltinModuleslist).rejectRestrictedGlobals, an eslint-scope-based check that throws on any unshadowed reference tofetch/XMLHttpRequest/WebSocket/EventSource— i.e. any reference that doesn't resolve to a local declaration or import sharing the same name, meaning it falls through to the real ambient global.rejectNodeBuiltinImports' doc comment and error message, which previously pointed to fetch-based/isomorphic APIs as the allowed escape hatch — no longer accurate now thatfetchitself is blocked too.this.parse(code)and before export extraction.node:fs, barefs,child_process,net,fs/promises), and edge cases (type-only imports, non-import statements).fetch()calls, referencingfetchwithout calling it,new XMLHttpRequest()/WebSocket()/EventSource()), and allowed cases (an imported action-catalog function, a locally-declared function or parameter that happens to be namedfetch— shadowing-safe)..backend.tsfile with anode:fsimport through the actual transform handler (using rollup's realparseAst, not a hand-built AST) to confirm the rejection fires through the genuine pipeline.QA Instructions
Build the plugin and link it into a scratch Vite project, then confirm a backend file importing a Node built-in — or referencing
fetch— is rejected while an ordinary backend file still transforms correctly.Blast Radius
.backend.tsfiles' static imports and top-level global references only. No feature flag — this is a build-time compile error for a pattern (Node built-ins, or raw network globals) that wasn't previously usable in production anyway, since production's real sandbox already blocks both.importspecifiers (notrequire()or a dynamically computedimport()); the global-reference check only catches references eslint-scope can't resolve to a local declaration/import of the same name..backend.tsfile that doesn't import a Node built-in or reference one of the four restricted globals directly (306/306 existing apps-plugin tests pass unchanged).Out of Scope / Follow-ups
backend-function-globals.d.ts(ambient TypeScript type for$that omitsDeno/process/Node-builtin globals).d.tsinto the publisheddist/tarball requires new build-tooling wiring inpackages/tools/src/rollupConfig.mjs(shared by all 5 published bundler plugins), which is disproportionate scope for this PR. Revisit once a scaffold tool exists to actually wire the type into a consumer'stsconfig.json.fetch; not blocking today's v1 rolloutDocumentation
.plans/high-code-apps-local-node-execution-design.md(dd-source repo)