Skip to content

apm approve / apm deny

Terminal window
apm approve [PACKAGE_REF...] [OPTIONS]
apm deny [PACKAGE_REF...] [OPTIONS]
apm policy explain <PACKAGE_REF>

APM blocks executable primitives (hooks, bin/ executables, self-defined MCP servers, and canvas extensions) from dependency packages by default. Trust is expressed through one noun, executables, across three layers:

LayerStoreWho manages itCommitted?Authority
Projectapm.yml executables.{allow,deny}Maintainer / CI setup (apm approve/apm deny)YesAdmin (shared)
User~/.apm/config.json executables.{allow,deny}apm approve --user / apm deny --userNoLowest; can only narrow
Orgapm-policy.yml executables:Org adminYes (policy repo)Ceiling on deny

apm approve adds a grant; apm deny adds a block. By default both write the project apm.yml (committed, so the whole team inherits the decision). --user writes your personal ~/.apm/config.json instead — a machine-local override that can only narrow trust, never widen past an org or project deny.

Text primitives (skills, agents, instructions) are never gated. Local project content (the root .apm/ directory) is always trusted.

TypeGatedNotes
Hooks (.apm/hooks/, hooks/)YesAuto-fire in IDE on lifecycle events
Bin executables (bin/)YesDeployed to agent PATH via symlinks
MCP servers (self-defined)Yesregistry: false servers write to IDE MCP config
Canvas extensions (.apm/extensions/)YesDeploys executable Node.js to IDE extensions
Text primitives (skills, agents, instructions)NoNo code execution risk

The install gate and apm audit resolve trust through one shared ladder. The first matching rung decides:

1. org deny_all / org deny -> denied (absolute ceiling)
2. user deny -> denied (narrowing)
3. project deny -> denied (committed narrowing)
4. project allow -> allowed
5. user allow -> allowed
6. org recommend -> allowed (user-overridable)
7. (no match) -> gated pending approval (denied but approvable)

Deny always wins. The org layer is the ceiling on deny — personal consent cannot widen past an org or project deny. The default (rung 7) is gated pending approval, not a hard deny: a package with executables and no opinion anywhere is parked until you approve it, and apm install still succeeds (see apm install).

There is no enforce mandate runtime, no cryptographic signing, and no content-hash binding in this release. An org executables.enforce rung degrades to recommend (allowed but still overridable by a deny).

The gate is enabled when any layer opts in: the project declares an executables: block (even empty {}), or the org policy carries a non-empty executables: block. Without any opt-in, executables deploy unconditionally (backward-compatible).

FlagDescription
PACKAGE_REFOne or more packages to approve (e.g. owner/repo).
--pendingList all packages with unapproved executables.
--allApprove all currently blocked packages.
--recommendedBulk-accept the org executables.recommend set.
--listShow the fleet-level effective trust decision and deciding layer per installed package.
--userWrite the grant to ~/.apm/config.json instead of apm.yml.
FlagDescription
PACKAGE_REFOne or more packages to deny. Denying a not-yet-installed package is allowed (a pre-emptive block).
--userWrite the block to ~/.apm/config.json instead of apm.yml.

apm policy explain <PACKAGE_REF> prints the effective executable-trust decision for a package: allowed or blocked per executable type, the deciding policy layer, and any shadowed (overridden) lower-authority layers. It is a subcommand of the apm policy group — the per-package companion to apm policy status (the policy-chain view).

Terminal window
apm policy explain owner/repo

For a fleet-level view, apm doctor runs an executable-trust drift check that flags any package allowed locally but denied by org policy and points to apm policy explain for the per-package detail.

The project executables.allow / executables.deny maps are keyed by owner/repo#version (or version-blind owner/repo) with per-type boolean flags:

# apm.yml (committed)
executables:
allow:
"owner/repo#1.2.0":
hooks: true
bin: true
deny:
"evil/pkg":
hooks: true
mcp: true
bin: true
canvas: true

The legacy top-level allowExecutables: block is deprecated. It is still read as an alias for executables.allow for one minor cycle and is migrated to executables.allow on the next apm approve / apm deny write. Prefer executables.allow.

The personal store uses the same shape under executables in ~/.apm/config.json. The standalone ~/.apm/approvals.yml file has been removed; its contents are migrated into ~/.apm/config.json automatically on first read.

Grant keys are package-scoped in v1: a bare owner/repo key and a owner/repo#1.2.0 key both match the package name regardless of the installed version. Use the versioned form for audit readability, not as a per-release trust boundary.

Approve a specific package (writes committed project trust):

Terminal window
apm approve owner/repo

Approve for this machine only:

Terminal window
apm approve --user owner/repo

List packages awaiting approval:

Terminal window
apm approve --pending

After review, approve everything still pending:

Terminal window
apm approve --all

Accept the org-recommended set:

Terminal window
apm approve --recommended

Inspect effective trust state across installed packages:

Terminal window
apm approve --list

Block a package (deny always wins):

Terminal window
apm deny evil/pkg

In CI environments (CI=true, APM_NON_INTERACTIVE=1, or when stdin is not a TTY), apm install parks unapproved executables and prints the approval remedy instead of prompting. Pre-approve packages by committing them to the project executables.allow block (the way to share trust via source control). Required-but-untrusted executables are enforced by apm audit through the required-executable-untrusted signal:

apm.yml
executables:
allow:
"ci-hooks/acme#1.2.0":
hooks: true
bin: true