Skip to content

security(#503 follow-up): site annotation // @perry-allow-dynamic should not be honored inside node_modules #996

@proggeramlug

Description

@proggeramlug

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

  • Site annotation honored in host source paths.
  • Site annotation ignored for any file under node_modules/.
  • Test: package under node_modules/evil/ with // @perry-allow-dynamic next to (process as any)[k]() still fails compilation.
  • Test: same site in host code still passes (regression guard).
  • Update docs/src/cli/dynamic-dispatch.md to 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 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions