Loading

    How to Block npm in Codex with Shims and Exec Policies

    John Lindquist
    John Lindquist
    Source Code

    AI coding agents might default to using npm to install packages, which can be undesirable if your project standardizes on pnpm, bun, or if you want to restrict package installation for security reasons. While Cursor's built-in execpolicy is a great first line of defense, a determined agent can sometimes find workarounds, like generating a script that calls npm indirectly.

    This lesson demonstrates a more robust, two-layered strategy to completely control the agent's environment. You'll learn how to combine a project-local shell shim with Cursor's execpolicy to ensure npm is disabled from all angles.

    The Strategy:

    1. Environment-Level Shim: By modifying the PATH environment variable for the agent's shell session, we can intercept any call to npm. We'll create a custom npm executable that prints a helpful message and exits, effectively blocking it at the source. This is configured via a project-local .codex/config.toml and corresponding .zsh startup files.
    2. Explicit execpolicy Rule: As a second layer, we'll define a standard execpolicy rule in .codex/rules to explicitly forbid the npm command. This catches direct invocations and provides clear feedback to the agent.

    By implementing both, you create a secure and controlled environment where the agent is guided to use your preferred tools, even if it tries to bypass standard restrictions. This technique gives you fine-grained control over the agent's capabilities, ensuring project consistency and security.

    How it Works

    The core of the environment-level restriction is a .codex/config.toml file that tells the zsh shell (used by Cursor) to load its configuration from a project-local directory.

    # .codex/config.toml
    # This tells zsh to load startup files from this project-local directory.
    [shell_environment_policy.set]
    ZDOTDIR = ".codex/zsh"

    Inside the .codex/zsh directory, a .zshenv or .zprofile file prepends our custom bin directory to the PATH.

    # .codex/zsh/.zshenv
    # Prepend the project-local shim directory to the PATH.
    export PATH="$PWD/.codex/bin:$PATH"

    This forces the shell to look in .codex/bin for executables first. We place our npm shim there, which intercepts the call.

    # .codex/bin/npm
    #!/usr/bin/env sh
    # This file intentionally has the same name as the real `npm` binary.
    # Because `.codex/zsh/` prepends `.codex/bin` to PATH, scripts that run `npm` by
    # name will hit this blocker before the system npm.
    # Print a message to stderr
    cat >&2 <<'EOF'
    npm is disabled inside this Codex project environment.
    Do not choose a replacement automatically. Ask the user whether they want pnpm, bun, or another approach.
    Common replacements: pnpm install/add/run, bun install/add/run.
    EOF
    # Exit with a "command not found" error code
    exit 127

    Finally, the execpolicy rule provides an additional, explicit block against direct npm calls.

    # .codex/rules/no-npm.rules
    # Block npm even when Codex sees an absolute path to an npm binary.
    prefix_rule(
    pattern = ["npm"],
    decision = "forbidden",
    justification = "npm is disabled in this demo. Ask whether to use pnpm, bun, or another package manager",
    match = [
    ["npm"],
    ["npm", "--version"],
    ],
    )

    Together, these two mechanisms create a comprehensive block, preventing both direct and indirect npm usage by the AI agent.

    Prompts

    Please run npm install jquery
    Run our test-npm.js

    Terminal Commands

    npm --version
    which npm
    codex
    # Executed inside the Codex/Cursor environment
    npm --version
    # Executed inside the Codex/Cursor environment
    which npm
    echo $ZDOTDIR
    Share