[APPS-2792] Fix: harden env-guard against bypass and reliability gaps - #510
Open
tyffical wants to merge 1 commit into
Open
Conversation
tyffical
force-pushed
the
tiffany.trinh/apps-2792-env-guard-hardening
branch
from
September 9, 2026 05:25
ea56b9c to
1e863ea
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This comment was marked as outdated.
This comment was marked as outdated.
tyffical
added this pull request to stack #499
September 9, 2026 13:12
tyffical
force-pushed
the
tiffany.trinh/apps-2792-env-guard-hardening
branch
from
September 9, 2026 15:33
1e863ea to
417f35c
Compare
tyffical
force-pushed
the
tiffany.trinh/apps-2792-env-guard-hardening
branch
from
September 9, 2026 16:49
bdc8106 to
602f611
Compare
tyffical
force-pushed
the
tiffany.trinh/apps-2792-env-guard-hardening
branch
from
September 9, 2026 18:28
602f611 to
b3d78a9
Compare
tyffical
force-pushed
the
tiffany.trinh/apps-2792-env-guard-hardening
branch
3 times, most recently
from
September 9, 2026 19:54
609af8e to
d717cda
Compare
…, cp options, and process.env assignment Fixes four bypass/reliability gaps found in review: - A FileHandle's own fd is caller-controlled and could be shadowed with an own property to report a harmless value to the environ-path check while the real read still operated on the handle's actual fd. Fixed by capturing FileHandle.prototype's native fd getter once, at patch-install time, and invoking it directly to bypass any later own-property shadow. - fs.cp/cpSync/promises.cp's recursive+dereference check read options.recursive/dereference once for its decision, then forwarded the caller's original options object to the real implementation, which read the same properties again — a getter-backed options object could report false to the check and true to the real call. Fixed by snapshotting both values into plain data properties before forwarding, the same pattern already used for options.fd. - Reassigning process.env rejected only null/undefined, letting a primitive (e.g. a number) through to become the new realEnv fallback and break every later unscoped access. Fixed by validating the runtime value is a non-null object. - fs.openAsBlob was wrapped unconditionally, even though it's absent on Node 18 — replacing the real `undefined` with an always-defined wrapper broke the existing feature-detection fallback in packages/core/src/helpers/fs.ts. Fixed by feature-detecting the same way before wrapping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tyffical
force-pushed
the
tiffany.trinh/apps-2792-env-guard-hardening
branch
from
September 9, 2026 19:56
d717cda to
2f5e268
Compare
tyffical
marked this pull request as ready for review
September 9, 2026 20:53
tyffical
requested review from
ksun154 and
setnilson
and removed request for
a team and
setnilson
September 9, 2026 20:53
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
env-guard.ts's coreprocess.envscoping mechanism.fs/process.report/util.inspect/Proxy surface. See the Changes table below for the full, itemized list.Architecture
All fixes land inside
env-guard.ts's existing scoping mechanism (introduced in #504) — this PR closes bypass and reliability gaps in that mechanism rather than changing its shape:16 changes across env-guard.ts, env-guard.test.ts, guarded-wrapper.ts
fs.read/readSync/readv/readvSyncand everyFileHandle.prototyperead method (read/readFile/readv/createReadStream/readableWebStream/readLines) are now guarded against the/proc/.../environbypasspackages/plugins/apps/src/vite/env-guard.tsFileHandleguard resolves the real fd via a getter captured once from the prototype at patch time, so a customer-controlled own property shadowing.fdwith a harmless value can't fool the checkpackages/plugins/apps/src/vite/env-guard.ts.fdgetter (and the guard's own logic) was previously invoked unconditionally on every callpackages/plugins/apps/src/vite/env-guard.tsfs.cp/cpSync/promises.cpnow reject arecursive: true, dereference: truecopy outright, since Node's own internal traversal for that case bypasses the top-level source-path checkpackages/plugins/apps/src/vite/env-guard.tsguardCpOptions/guardEnvironPathOrFdOptionread a getter-backed option value exactly once via destructuring, instead of risking a second, differently-answered read during a later spreadpackages/plugins/apps/src/vite/env-guard.tsfs.openAsBlobandfs.FileReadStreamare now guarded, each gated on the real function actually existing on the running Node versionpackages/plugins/apps/src/vite/env-guard.tsprocess.envtonull,undefined, or a primitive (not an object) is now rejected instead of silently corrupting the real environment fallbackpackages/plugins/apps/src/vite/env-guard.tsObject.defineProperty(process.env, key, { configurable: false })now throws a clear, guard-specific error instead of a native Proxy invariantTypeError— a non-configurable definition can never be satisfied against the Proxy's permanently-empty targetpackages/plugins/apps/src/vite/env-guard.tsprocess.env's Proxy now targets a permanently-empty dummy object, closingutil.inspect(process.env)reading the real environment straight off the Proxy's internal target — both default mode andshowProxy: truebypass every trap by design, per Node's own docspackages/plugins/apps/src/vite/env-guard.tsprocess.report.writeReport()refuses a verified non-regular destination (FIFO/socket/character device), re-checked immediately before the real write (not just once, earlier in the function) to narrow the window for an external symlink swappackages/plugins/apps/src/vite/env-guard.tsexcludeEnvwrite (made from outside an active scope while a different scope is active) is now validated immediately via Node's native setter instead of skipping that validationpackages/plugins/apps/src/vite/env-guard.tswrapGuardedAsyncFsFn'sonResolvedhook (used byfs.promises.open) now forwards the caller'sthisthrough to the wrapped function, instead of losing it on a bare callpackages/plugins/apps/src/vite/env-guard.tspackages/plugins/apps/src/vite/env-guard.tsopenAsBlob/FileReadStreamguards, cp TOCTOU, primitiveprocess.envrejection, thedefinePropertyinvariant, and the existing/proc/.../environ/scope tests extended to the newly-guarded entry pointspackages/plugins/apps/src/vite/env-guard.test.tsmakeGuardWrapper/makeGuardCallbackWrapperdoc comment tightenedpackages/plugins/apps/src/vite/guarded-wrapper.tsQA Instructions
Manual QA: standalone-process reproduction for the util.inspect(process.env) fix (click to expand — the only fix here that needs a genuinely fresh OS process, not just an in-process Jest test)
Every other fix in this PR is exercised directly by a named test in
env-guard.test.ts(runyarn test:unit packages/plugins/apps/src/vite/env-guard.test.tsto reproduce all of them). Theutil.inspectfix needs a fresh, never-before-scopedprocess.envto observe the Proxy's real target, which a single Jest test file (already sharing the module's installed state across its own tests) can't produce on its own — verified instead via a standalone script:npx tsx /tmp/inspect-proxy-repro.mjs # NOT LEAKED ✅ VERIFIEDManual QA: env-guard's process.env scoping under a real end-to-end $.Actions call (click to expand — exercises the guard inside the actual dev-server request path, not just Jest)
POST /__dd/executeAction→ staging auth → runtime-context priming (preview-async, long-polled) → in-process execution of the function body underrunWithScopedEnv→ a live$.Actionsproxy call.@datadog/vite-pluginbuilt from this branch (yarn buildinpackages/published/vite-plugin) and imported by absolute path in the app'svite.config.ts, sincepublishConfig-basedexportsonly resolve via a realnpm publish, not a local link.Blast Radius
Out of Scope / Follow-ups
1 item deferred
process.report.writeReport()'s TOCTOU window is narrowed (re-checked immediately before the real write) but not fully closed — a symlink swap in the remaining gap between that check and Node's own write syscall is still theoretically possibleDocumentation