Skip to content

[APPS-2792] Add: runtime network/subprocess guard for local execution - #484

Draft
tyffical wants to merge 1 commit into
tiffany.trinh/apps-2792-wire-into-dev-serverfrom
tiffany.trinh/apps-2792-runtime-network-guard
Draft

[APPS-2792] Add: runtime network/subprocess guard for local execution#484
tyffical wants to merge 1 commit into
tiffany.trinh/apps-2792-wire-into-dev-serverfrom
tiffany.trinh/apps-2792-runtime-network-guard

Conversation

@tyffical

@tyffical tyffical commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Motivation

Architecture

net.Socket.prototype.connect, globalThis.fetch, and child_process's spawn/exec/execSync are real, process-wide singletons — network-guard.ts monkey-patches them directly rather than sandboxing the customer's module, since there's no process boundary to sandbox with. That makes the guard a single shared piece of mutable state (allowDepth + the saved originals) threaded through one execution's lifetime:

runScriptLocally
      │
      ▼
┌───────────────────────────────┐
│ runBlocked(fn)                │  applyPatches()
│ BLOCKED: net.Socket.connect,  │  → net.Socket.connect   throws
│ fetch, child_process all      │  → fetch                rejects
│ throw/reject                  │  → spawn/exec/execSync  throw
└───────────────┬────────────────┘
                │  customer's fn() runs
                ▼
  fn() calls $.Actions.a() and $.Actions.b() concurrently (Promise.all)
                │
      ┌─────────┴──────────┐
      ▼                    ▼
 runAllowed(a)         runAllowed(b)
 allowDepth 0→1        allowDepth 1→2
 restorePatches()      (already restored — no-op)
      │                    │
      ▼                    ▼
┌────────────────────────────────────┐
│ ALLOWED (allowDepth > 0)           │
│ real net/fetch/spawn restored —    │
│ only inside executeAction          │
└──────┬───────────────────────┬─────┘
       │ b resolves first      │ a still in flight
       ▼                       │
 allowDepth 2→1                │
 (still > 0 → stays ALLOWED) ──┘
       │
       │ a resolves
       ▼
 allowDepth 1→0 → applyPatches() → BLOCKED again
       │
       │  fn() returns
       ▼
 runBlocked's finally: restorePatches()
       │
       ▼
   UNBLOCKED (real functions, for whatever
   the dev server does next)

The ref-count (allowDepth), not a boolean, is what makes the overlap safe: two concurrent $.Actions calls each bump it on entry and drop it on exit, and the guard only re-blocks once the last one exits — a boolean would re-block the instant the faster of two overlapping calls finished, breaking the slower one mid-flight.

runBlocked/runAllowed's own try/finally only unwinds when fn actually settles. runScriptLocally's timeout wraps the whole thing in Promise.race([run(), timeout]), which abandons rather than cancels the loser — a customer function that never resolves means run() (and the runBlocked inside it) never reaches its finally, so without a separate backstop the block would stay applied for the rest of the process once the timeout fires. forceReset() is that backstop: called directly from the timer callback (unconditionally restoring the real functions and zeroing allowDepth) the moment the timeout fires, independently of whether the abandoned run() ever settles — alongside local-execution.ts's own epoch-gated poisonActionCatalogRegistration() call in the same timer (from #480), since both are closing the same class of "abandoned execution left shared state pointing the wrong way" gap. The same function is used as a Jest afterEach in network-guard.test.ts/local-execution.test.ts, for the identical reason at the test level — these are real Node singletons, not per-test-file sandboxed state, so a test that leaves them patched leaks into every test that runs after it in the same Jest worker, including unrelated test files.

Changes

What changed File
New runBlocked(fn): monkey-patches net.Socket.prototype.connect, fetch, and child_process's spawn/exec/execSync to throw/reject for the duration of fn, restoring the real implementations in a finally regardless of how fn completes. network-guard.ts
New runAllowed(fn): temporarily restores real network access for the duration of fn, ref-counted (not a boolean) so two $.Actions calls overlapping within a single execution (e.g. inside a Promise.all) don't re-block network on each other mid-flight. network-guard.ts
New forceReset(): unconditionally restores the real functions and zeroes allowDepth, independent of runBlocked/runAllowed's own finally — the backstop for a fn that's abandoned (timeout) or a test that fails to clean up after itself. network-guard.ts
runScriptLocally now wraps the customer's function call (only — not the loadModule/registration calls before it, which need no network) in runBlocked, and calls forceReset() from the timeout timer itself so an abandoned, still-running hung function can't leave network/subprocess access blocked for the rest of the process. local-execution.ts
makeActionsProxy's apply trap now wraps its executeAction call in runAllowed — the one sanctioned network path, exempted from the block. local-execution.ts
Unit tests for every patched target and both directions (block + restore, restore-on-throw, no state leak across separate runBlocked calls, nested runAllowed exemption, concurrent-overlap ref-counting, re-block-on-throw). A Jest afterEach calls forceReset() unconditionally as a hard safety net, independent of any test's own cleanup. network-guard.test.ts
Integration tests confirming the guard is actually wired into executeScriptLocally: a customer function using raw net/fetch/child_process is rejected; a real $.Actions call still succeeds; network is restored after the execution finishes, including after a timeout abandons a hung function; two real $.Actions calls made concurrently via Promise.all keep network allowed through the entire overlap, exercised through the real executeScriptLocallymakeActionsProxy path (not just the unit-level runAllowed). Same afterEach safety net as above. local-execution.test.ts

QA Instructions

yarn install
yarn test:unit packages/plugins/apps/src/vite/network-guard.test.ts
# Expected: Test Suites: 1 passed / Tests: 11 passed ✅ VERIFIED
yarn test:unit packages/plugins/apps/src/vite/local-execution.test.ts
# Expected: Test Suites: 1 passed / Tests: 30 passed ✅ VERIFIED
yarn test:unit packages/plugins/apps
# Expected: Test Suites: 25 passed / Tests: 337 passed ✅ VERIFIED
yarn workspace @dd/apps-plugin run typecheck
# Expected: no output, clean exit ✅ VERIFIED
npx eslint 'packages/plugins/apps/**/*.ts' packages/tests/src/_jest/helpers/mocks.ts --quiet
# Expected: no output, clean exit ✅ VERIFIED

Coverage note: this repo's Jest collectCoverageFrom CLI flag didn't produce a usable per-file report for either new/changed file in this environment (pre-existing tooling quirk, not introduced by this change — the coverage table only ever listed _jest helper files regardless of the glob passed). Manually verified every branch in network-guard.ts is exercised by at least one test.

This module isn't independently reachable from a real npm run dev session on its own — that requires #481. It was exercised as part of a real, combined manual QA pass across the full stack (see #481's QA Instructions): a real scaffolded app, running with the full stack merged locally, confirmed the network guard correctly blocks raw net/fetch in a customer function while still letting a real $.Actions call through — end-to-end, not just at the unit-test level.

Blast Radius

  • No behavior change for any currently-shipping code path — same as [APPS-2792] Add: in-process local execution for backend functions #479/[APPS-2792] Add: harden the in-process local execution path #480, this stack isn't released yet.
  • Scoped precisely to the duration of a local execution's customer-function call; the dev server's own network use (before/after that window, and anything unrelated to local-execution.ts) is never touched.
  • forceReset() on timeout narrows, rather than eliminates, an existing gap: the abandoned (not cancelled) hung function keeps running with real network access restored early rather than staying blocked forever — bounded to that one already-abandoned execution, versus the alternative of leaving every future execution in the same dev server process permanently blocked until restart.
  • Risk: low. Additive, defense-in-depth only — closes a gap that only matters for local-dev-loop safety/prod-parity, not a new production security boundary (production's own Deno sandbox is unaffected and remains the real boundary).

Out of Scope / Follow-ups

Item Status Next step
Native addon bypassing Node's JS-level net stack entirely Accepted residual gap Narrower and rarer than the pure-JS case this closes (most native modules are for CPU-bound work, not networking) — not worth the false-positive risk of blocking native addon loading outright
dns.lookup interception Out of scope Low realistic benefit for this threat model (dev-loop safety, not defending against deliberate DNS-tunneling exfiltration) — would risk breaking legitimate hostname validation for no real gain
A hung customer function is abandoned, not cancelled, on timeout — it keeps running in the background with real network access restored (see Blast Radius) Accepted residual gap Would need real cancellation (e.g. an AbortSignal threaded through the customer's own function, which we don't control) or re-architecting local execution onto a worker thread that can be killed outright — bigger change than this PR's scope

Documentation

@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented Aug 8, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 2 Pipeline jobs failed

Continuous Integration | Unit tests — ❌ 171 tests failed · 🔧 Needs a code fix, caused by this PR

View more details · View in GitHub Actions

171 failed tests due to 'expect(stateLogs).toBeDefined()' received undefined.

❌ Injection Plugin Builds Easy build with injections esbuild | 3.2.12 Normal log in easy build Should have output the expected logs from execution. from ../plugins/injection/src/index.test.ts
expect(received).toBeDefined()

Received: undefined
❌ Injection Plugin Builds Easy build with injections esbuild | 3.2.12 [after] code injection in easy build Should have output the expected logs from execution. from ../plugins/injection/src/index.test.ts
expect(received).toBeDefined()

Received: undefined
❌ Injection Plugin Builds Easy build with injections esbuild | 3.2.12 [after] distant file injection in easy build Should have output the expected logs from ex... from ../plugins/injection/src/index.test.ts
expect(received).toBeDefined()

Received: undefined
↳ and 168 more — View all
Continuous Integration | End to End

View more details · View in GitHub Actions

Timeout waiting for 'beforeAll' hook to complete for multiple tests. Please check asynchronous setup operations.

📋 Copy fix prompt
CI on my pull request is failing. Help me find and fix the root cause of each failing job below — they were flagged as caused by changes in this PR, so focus on the diff. For each job, explain the failure and propose a fix.

Before you start, set up the Datadog software-delivery tooling so you can
query the CI data yourself:

1. Check whether you already have the Datadog software-delivery MCP tools
   (e.g. a `search_datadog_ci_pipeline_events` tool) and the `unblock-pr` skill.
2. If either is missing, STOP and ask me for permission before installing
   anything. Do not install or run anything until I have said yes.
3. Only with my explicit approval, set up the Datadog software-delivery MCP
   server and skills by following:
     https://docs.datadoghq.com/getting_started/software_delivery_mcp_tools/
   then restart so the skill is picked up.
4. If I decline, skip all of the above and work from the context below alone.

Then run /unblock-pr — it will pull the CI data itself. The job context below is what we already know.

If /unblock-pr is not available — because I declined the setup above, or it did not install — work from the context below instead.

Datadog has already classified this failure as caused by changes in this PR.
Take that as given and work the fix:

1. Locate the change. Diff this branch against its base and find the change
   that produces this error. Explain the mechanism, don't just name a file:
     git fetch origin && git diff $(git merge-base origin/tiffany.trinh/apps-2792-wire-into-dev-server HEAD)...HEAD
2. Reproduce it locally. Run the failing job's command or test before
   proposing anything.
3. Propose the smallest fix that addresses the root cause — not a workaround,
   not a broadened assertion, not a disabled or skipped test.
4. Re-run the same command to confirm, and say exactly what you ran.
5. If the failure turns out to be intermittent rather than deterministic, say
   so plainly instead of "fixing" it — that is a flaky test, and patching it
   hides the problem.

If the right move is to re-run the job rather than change code, use the job
link in the context below. For GitHub Actions: `gh run rerun <run-id> --failed`,
where the run ID is the number after `/runs/` in that URL (not the trailing
number, which is the job ID).

Branch: tiffany.trinh/apps-2792-runtime-network-guard

Continuous Integration | Unit tests
Commit: b35a2d27b86eebd3738d73cb1593fd5e25238043
Error (code / test):
171 failed tests due to 'expect(stateLogs).toBeDefined()' received undefined.
CI job: https://github.com/DataDog/build-plugins/actions/runs/33045230578/job/98427566840

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: aff9ee7 | Docs | View more details | Give us feedback!

tyffical added a commit that referenced this pull request Aug 10, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
tyffical added a commit that referenced this pull request Aug 20, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from e48778e to 2ad1ce9 Compare August 20, 2026 22:19
@tyffical
tyffical changed the base branch from tiffany.trinh/apps-2792-harden-local-execution-v2 to tiffany.trinh/apps-2792-wire-into-dev-server August 20, 2026 22:25
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 2ad1ce9 to b69e5f2 Compare August 20, 2026 22:28
tyffical added a commit that referenced this pull request Aug 20, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from b69e5f2 to 63539eb Compare August 20, 2026 23:31
tyffical added a commit that referenced this pull request Aug 20, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 63539eb to 1b11592 Compare August 20, 2026 23:40
tyffical added a commit that referenced this pull request Aug 21, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 1b11592 to 9d591ec Compare August 21, 2026 03:54
@tyffical
tyffical requested a balanced review from Copilot August 21, 2026 16:24
@DataDog DataDog deleted a comment from chatgpt-codex-connector Bot Aug 21, 2026
@tyffical

Copy link
Copy Markdown
Contributor Author

@cursor review
@codex review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Friend, this PR adds runtime restrictions for in-process local backend execution.

Changes:

  • Adds process-wide network and subprocess guards.
  • Exempts $.Actions calls and resets guards after timeouts.
  • Adds unit and integration coverage for guard behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
network-guard.ts Implements blocking, exemptions, and reset logic.
network-guard.test.ts Tests guard state and concurrency.
local-execution.ts Integrates guards into local execution.
local-execution.test.ts Tests execution-path guard behavior.
Suppressed comments (1)

packages/plugins/apps/src/vite/network-guard.ts:165

  • runAllowed can run after its enclosing blocked scope has already been reset. In the existing abandoned-execution scenario, a late call through a captured $.Actions proxy increments from zero, the guarded action rejects, and this applyPatches() then leaves the whole process blocked even though no runBlocked is active; the test's afterEach(forceReset) masks the leak. Track whether this call actually entered from an active blocked scope and only reapply in that case, or perform the abandoned check before entering runAllowed.
        if (currentGeneration === myGeneration) {
            allowDepth -= 1;
            if (allowDepth === 0) {
                applyPatches();
            }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/plugins/apps/src/vite/network-guard.ts Outdated
Comment on lines +428 to +433
// Blocks net/fetch/child_process for the duration of the customer's
// function call only — loadModule and the registration calls above
// (both Vite's own transform pipeline, no network) run unguarded.
// $.Actions calls made from inside fn are exempted via `runAllowed`
// in `makeActionsProxy`. See network-guard.ts.
const result = await runBlocked(() => fn(...args));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried fixing this by moving runBlocked to wrap loadModule itself, but reverted it — Vite's real ssrLoadModule pipeline needs genuine network/fs access internally to transform and resolve the customer's module, and blocking that broke the real dev-server integration test outright (not just theoretical: a real @datadog/apps-backend import through a real Vite server started returning 500). Documented as an accepted residual gap in network-guard.ts's own doc comment, alongside the existing native-addon and dgram gaps, rather than engineered around further for now. Leaving unresolved to keep it tracked.

Comment thread packages/plugins/apps/src/vite/network-guard.ts Outdated
Comment on lines +88 to +104
function restorePatches(): void {
if (savedConnect) {
net.Socket.prototype.connect = savedConnect;
}
if (savedFetch) {
globalThis.fetch = savedFetch;
}
if (savedSpawn) {
child_process.spawn = savedSpawn;
}
if (savedExec) {
child_process.exec = savedExec;
}
if (savedExecSync) {
child_process.execSync = savedExecSync;
}
}
chatgpt-codex-connector[bot]

This comment was marked as resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b36f55e0bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// (both Vite's own transform pipeline, no network) run unguarded.
// $.Actions calls made from inside fn are exempted via `runAllowed`
// in `makeActionsProxy`. See network-guard.ts.
const result = await runBlocked(() => fn(...args));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip guard entry after an execution already timed out

When the timeout fires while loadModule(...) is still pending, forceReset() clears the guard and releases the queue, but the abandoned run() continues and enters this runBlocked call once loading completes. If a newer execution is already blocked, the stale call overwrites its saved snapshots and generation; when either call finishes, the process can be left permanently patched, and a stale function that hangs leaves the same result. Check abandoned before invoking the function/entering a new guard scope.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same finding as the copilot review comment on this line — see my reply there. Traced through carefully and it doesn't currently reproduce; added a regression test proving it (2c6aa37d).

Comment thread packages/plugins/apps/src/vite/network-guard.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

// (both Vite's own transform pipeline, no network) run unguarded.
// $.Actions calls made from inside fn are exempted via `runAllowed`
// in `makeActionsProxy`. See network-guard.ts.
const result = await runBlocked(() => fn(...args));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traced through carefully — this doesn't currently reproduce. The if (abandoned) throw check immediately before entering runWithScopedEnv/runBlocked runs synchronously with no await in between, so there's no window for the timeout's setTimeout callback to interleave and flip abandoned to true after the check but before the guards are entered. Added a regression test (2c6aa37d) that specifically simulates this: A's own loadModule for its main function body resolves late, after B (a newer execution) has already started and is still running its own body inside runBlocked/runWithScopedEnv — A correctly bails via the abandoned check without ever touching the guards, leaving B's state untouched.

Comment on lines +263 to +288
const abandonedAction = runAllowed(
() =>
new Promise<void>((resolve) => {
resolveAbandonedAction = resolve;
}),
);

// Simulates the timeout handler abandoning this execution while
// the $.Actions call above is still in flight.
forceReset();

// A newer execution starts, and its own legitimate $.Actions call
// must be correctly allowed through and re-blocked afterward.
const result = await runBlocked(async () => {
await runAllowed(async () => 'newer allowed call');
await expect(fetch('https://example.com')).rejects.toThrow(
/Network access is not allowed/,
);
return 'newer execution result';
});
expect(result).toBe('newer execution result');

// The abandoned call's runAllowed finally now fires, well after
// being superseded — it must not touch allowDepth.
resolveAbandonedAction?.();
await abandonedAction;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permanent-property redesign in 68ed39f replaces the old generation-counter check this comment was about — runAllowed now checks hasActiveScope() on a shared epoch guard, and calling it with no active runBlocked scope is a deliberate, meaningful code path (an abandoned execution's late-settling $.Actions call). The existing tests "Should not let an abandoned runAllowed call's late settlement affect later executions" and "Should treat a runAllowed call that only starts after its execution was already abandoned as a no-op..." already exercise exactly that path and assert it doesn't wedge the guard.

@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 51f9d73 to ceeece1 Compare August 25, 2026 04:41
@tyffical
tyffical requested a balanced review from Copilot August 25, 2026 15:44
chatgpt-codex-connector[bot]

This comment was marked as resolved.

This comment was marked as resolved.

@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 2592d9b to 8d82135 Compare August 25, 2026 16:34
tyffical added a commit that referenced this pull request Aug 25, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch 2 times, most recently from 6720f6b to 07cca79 Compare August 26, 2026 01:46
tyffical added a commit that referenced this pull request Aug 26, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484)
each defined their own near-identical LoadModule resolver double. Factor
the common resolve-or-throw logic into moduleResolverFor in the shared
mocks helper so both can build on it instead of duplicating it.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch 2 times, most recently from ea84a47 to 4fd59b3 Compare August 26, 2026 02:35
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 4fd59b3 to cb79658 Compare August 26, 2026 04:32
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from cb79658 to affa16b Compare August 26, 2026 16:15
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from affa16b to fbad040 Compare August 26, 2026 17:31
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from fbad040 to 085afa1 Compare August 26, 2026 18:09
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 085afa1 to 64e9169 Compare August 27, 2026 06:05
Runs the customer function's own call (not loadModule or the action-catalog/
apps-backend registrations, which need real network/fs access) with global
net/fetch/child_process access blocked, hardened against every bypass a
review pass found: a malicious result's toJSON()/getter still running under
the block, and $.Actions/action-catalog calls made from inside the function
exempted via runAllowed so the guard doesn't also block trusted API calls.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-runtime-network-guard branch from 64e9169 to aff9ee7 Compare August 27, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants