Follow-up to #503 / #941.
Problem
The dynamic-stdlib-dispatch refusal landed in #941 honors // @perry-allow-dynamic site annotations uniformly, regardless of whether the source file lives in host code or under node_modules/. Since the threat model is malicious npm packages, a hostile package can defeat the check by emitting the annotation next to its own call:
// in node_modules/evil-dep/index.js
const k = atob("ZXhpdA==");
// @perry-allow-dynamic
(process as any)[k](0);
The per-package allow-list (perry.allowDynamicStdlibDispatch: ["pkg"]) is host-controlled and works correctly. The site annotation is the only opt-out a dependency can grant itself.
Proposal
When the currently-lowering source path matches node_modules/, ignore // @perry-allow-dynamic. Dependencies that legitimately need dynamic dispatch must be opted in by the host via the per-package allow list (or the global flag). User code is unaffected.
Mechanically: in lower_member.rs where the site-annotation check happens, gate it on crate::ir::package_name_for_source_path(&ctx.source_file_path).is_none() (i.e. host code only). The helper already exists from #941.
Acceptance
Minor nits worth folding in
While touching this code, also fix the doc drift in crates/perry-hir/src/ir.rs: the thread-local docstring for REFUSE_DYNAMIC_STDLIB_DISPATCH lists buffer (intentionally excluded — Buffer is a constructor) and omits http2, async_hooks, readline, string_decoder, tty, worker_threads (which are in STDLIB_NAMESPACE_NAMES).
Follow-up to #503 / #941.
Problem
The dynamic-stdlib-dispatch refusal landed in #941 honors
// @perry-allow-dynamicsite annotations uniformly, regardless of whether the source file lives in host code or undernode_modules/. Since the threat model is malicious npm packages, a hostile package can defeat the check by emitting the annotation next to its own call:The per-package allow-list (
perry.allowDynamicStdlibDispatch: ["pkg"]) is host-controlled and works correctly. The site annotation is the only opt-out a dependency can grant itself.Proposal
When the currently-lowering source path matches
node_modules/, ignore// @perry-allow-dynamic. Dependencies that legitimately need dynamic dispatch must be opted in by the host via the per-package allow list (or the global flag). User code is unaffected.Mechanically: in
lower_member.rswhere the site-annotation check happens, gate it oncrate::ir::package_name_for_source_path(&ctx.source_file_path).is_none()(i.e. host code only). The helper already exists from #941.Acceptance
node_modules/.node_modules/evil/with// @perry-allow-dynamicnext to(process as any)[k]()still fails compilation.docs/src/cli/dynamic-dispatch.mdto spell out that the annotation is host-code only; dependencies opt in via host config.Minor nits worth folding in
While touching this code, also fix the doc drift in
crates/perry-hir/src/ir.rs: the thread-local docstring forREFUSE_DYNAMIC_STDLIB_DISPATCHlistsbuffer(intentionally excluded — Buffer is a constructor) and omitshttp2,async_hooks,readline,string_decoder,tty,worker_threads(which are inSTDLIB_NAMESPACE_NAMES).