Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Executable Trust Governance v1 (#1873): executable trust is now one concept
with one resolver and deny-wins precedence. Organizations can now declare an
`executables:` block in `apm-policy.yml` (`deny_all`, `deny`, `require`,
`recommend`) that is carried through policy inheritance, closing the
GRANT/MANDATE asymmetry where projects could allow executables but orgs
could not deny them. A single deny-wins precedence resolver
(`resolve_exec_decision`) is now shared by both the install gate and the
`apm audit` policy checks, so the gate and the audit can never disagree.
Precedence (first match wins): org `deny_all`/`deny` > user deny > project
deny > project allow > user allow > org `recommend` > default-deny. The lockfile records a
per-dependency `exec_status` (`deployed`, `gated_pending_approval`,
`denied`, `absent`). No cryptographic signing or `enforce`-mandate
execution is introduced in v1 (an unverified `enforce` rung fail-safe
degrades to `recommend`). (by @sergio-sisternes-epam) (#1873)
- `apm policy explain <pkg>` prints the effective executable-trust decision
for a package: whether it is allowed, the deciding policy layer, and any
layers it shadows. `apm doctor` adds a fleet-level executable-trust drift
check that flags packages allowed locally but denied by org policy. (#1873)
- `apm approve --recommended` bulk-accepts an organization's `recommend`
set, and `apm approve --list` shows the effective trust state of every
installed package with executables. (#1873)
- Org-wide policy discovery now cascades through candidate repo names
(`.github`, then `.apm`, then `_apm`) and speaks the Azure DevOps Items
API, so Azure DevOps organizations -- which forbid repo names that begin
or end with `.` -- can host an APM governance policy repo for the first
time. (by @sergio-sisternes-epam; closes #1813) (#1830)

### Changed

- Executable-trust vocabulary is unified onto one noun, `executables`.
`apm approve` / `apm deny` now default to the project `apm.yml`
`executables: {allow, deny}` block (the committed, team-wide admin
decision); pass `--user` to write personal consent to
`~/.apm/config.json` (the lowest-authority, machine-local override that
can only narrow). (#1873)
- The `required-packages-deployed` audit check now asserts package
PRESENCE in the lockfile rather than materialized `deployed_files`, so an
install SUCCEEDS when a required package is present-but-parked (its
executables gated pending approval) and prints a one-command remedy
instead of hard-failing. A separate `required-executable-untrusted`
signal hard-fails CI when a required package's executables are untrusted.
(#1873)

### Deprecated

- The project `allowExecutables:` block is deprecated in favor of
`executables.allow`. It remains a read alias for one minor cycle and is
migrated to `executables.allow` on the next `apm approve`/`apm deny`
write. The org `bin_deploy` deny policy is folded into
`executables.deny[bin]` as a deprecated alias. (#1873)

### Removed

- The standalone `~/.apm/approvals.yml` personal-consent file is removed;
its contents are migrated into `~/.apm/config.json` under
`executables: {allow, deny}` on first read (net-new control-surface
files = 0). (#1873)

### Fixed

- `apm install <pkg>@<marketplace>` now preserves GitLab and other
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/concepts/primitives-and-targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Model Context Protocol servers declared as dependencies. APM writes the per-harn

### Canvas extensions (experimental)

GitHub Copilot CLI canvas extensions: a directory bundle whose entry file is `extension.mjs` (executable Node.js). Copilot-only. Behind the `canvas` experimental flag; dependency-provided canvases are blocked unless approved via `allowExecutables` in `apm.yml` and `apm approve <pkg>`, because they are arbitrary executable code. Project scope deploys to `.github/extensions/`; `--global` deploys a dependency canvas to `~/.copilot/extensions/` (always requiring approval).
GitHub Copilot CLI canvas extensions: a directory bundle whose entry file is `extension.mjs` (executable Node.js). Copilot-only. Behind the `canvas` experimental flag; dependency-provided canvases are blocked unless approved via the `executables` block in `apm.yml` and `apm approve <pkg>`, because they are arbitrary executable code. Project scope deploys to `.github/extensions/`; `--global` deploys a dependency canvas to `~/.copilot/extensions/` (always requiring approval).

- Source: `.apm/extensions/<name>/extension.mjs`
- Deploys to: `.github/extensions/<name>/` (project) or `~/.copilot/extensions/<name>/` (`--global`)
Expand Down Expand Up @@ -135,7 +135,7 @@ How to read a cell:
- `commands / copilot = unsupported` -- Copilot has no commands primitive; the same source `.prompt.md` reaches Copilot as a native prompt instead.
- `plugins / *` -- APM unpacks the plugin at install time into the primitives in the rows above; routing then follows those rows.
- `MCP servers / *` -- APM writes the harness's standard MCP config. Transitive MCP servers brought in by deep dependencies must be explicitly declared or trusted with `--trust-transitive-mcp` -- effectively `gated` for those, `native` for direct dependencies.
- `canvas / copilot = gated` -- requires the `canvas` experimental flag; a canvas shipped by a dependency is executable code, so it stays blocked until the package is approved via `allowExecutables` in `apm.yml` (`apm approve <pkg>`). First-party canvases in your own package deploy at project scope once the flag is on. With `--global`, a dependency canvas deploys to `~/.copilot/extensions/` and always requires approval (first-party global install is not supported). Every other harness is `unsupported`: a canvas is a Copilot CLI construct only.
- `canvas / copilot = gated` -- requires the `canvas` experimental flag; a canvas shipped by a dependency is executable code, so it stays blocked until the package is approved via the `executables` block in `apm.yml` (`apm approve <pkg>`). First-party canvases in your own package deploy at project scope once the flag is on. With `--global`, a dependency canvas deploys to `~/.copilot/extensions/` and always requires approval (first-party global install is not supported). Every other harness is `unsupported`: a canvas is a Copilot CLI construct only.

## Where compiled context files land

Expand Down
49 changes: 48 additions & 1 deletion docs/src/content/docs/enterprise/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ APM has no runtime footprint. Once `apm install` or `apm compile` completes, the

- **No runtime component.** APM generates files then terminates. It does not run alongside your application.
- **No network calls after install.** All network activity (git clone/fetch) occurs during dependency resolution. There are no callbacks, webhooks, or phone-home requests.
- **No arbitrary code execution.** APM does not execute scripts from packages, evaluate expressions in templates, or run downloaded code. (**Canvas exception:** the experimental `canvas` primitive deploys executable `extension.mjs` (Node.js) code to `.github/extensions/` or `~/.copilot/extensions/`; this surface is gated by both the `canvas` experimental flag and, when the project opts in via `allowExecutables:` in `apm.yml`, the `allowExecutables` approval gate for dependency-provided canvases. See [Canvas extensions](/apm/integrations/canvas/).)
- **No arbitrary code execution.** APM does not execute scripts from packages, evaluate expressions in templates, or run downloaded code. (**Canvas exception:** the experimental `canvas` primitive deploys executable `extension.mjs` (Node.js) code to `.github/extensions/` or `~/.copilot/extensions/`; this surface is gated by both the `canvas` experimental flag and the [executable trust gate](#executable-trust-gate) for dependency-provided canvases. See [Canvas extensions](/apm/integrations/canvas/).)
- **No access to application data.** APM never reads databases, API responses, application state, or user data.
- **No persistent background processes.** APM does not install daemons, services, or scheduled tasks.
- **No telemetry or data collection.** APM collects no usage data, analytics, or diagnostics. Nothing is transmitted to Microsoft or any third party.
Expand Down Expand Up @@ -304,6 +304,53 @@ across targets.
| **OpenCode** | `.opencode/commands/*.md` | Deployed when `.opencode/` exists. |
| **Gemini CLI** | `.gemini/commands/*.toml` | Deployed when `.gemini/` exists. |

## Executable trust gate

APM blocks executable primitives from dependency packages by default: hooks,
`bin/` executables, self-defined MCP servers (`registry: false`), and canvas
extensions. Text primitives (skills, agents, instructions) are never gated, and
local root `.apm/` content is always trusted.

Trust is expressed through one noun, `executables`, across three layers, and the
install gate and `apm audit` resolve it through a single deny-wins,
first-match-wins ladder:

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

- **Org** (`apm-policy.yml` `executables:`) is the ceiling on deny. It can
`deny_all`, `deny` packages, `require` packages be present and trusted, and
`recommend` a vetted set. See [executables](../reference/policy-schema/#executables) in the policy
schema.
- **Project** (`apm.yml` `executables.{allow,deny}`) is committed admin trust,
shared with the team.
- **User** (`~/.apm/config.json` `executables.{allow,deny}`) is the lowest
authority -- a machine-local override that can only narrow, never widen past
an org or project deny.

Personal consent can never widen past an org deny, and the default (rung 7) is
**gated pending approval** -- a package with executables and no opinion anywhere
is parked until approved, not hard-denied. This release ships no `enforce`
mandate runtime, no signing, and no content-hash binding; an org
`executables.enforce` rung degrades to `recommend`.

Each locked dependency records its resolved state in the `exec_status` field of
`apm.lock.yaml` (`deployed`, `gated_pending_approval`, `denied`, or `absent`).
For CI, `apm install` succeeds when a required package is present-but-parked and
prints a one-command remedy (e.g. `apm approve <pkg>`); a separate audit signal,
`required-executable-untrusted`, hard-fails when a required package's
executables are untrusted. Manage trust with [`apm approve` / `apm
deny`](../reference/cli/approve/), inspect the deciding layer for one
package with `apm policy explain <pkg>`, and surface fleet-wide layer
conflicts with `apm doctor`.

## MCP server trust model

APM integrates MCP (Model Context Protocol) server configurations from packages. Trust is explicit and scoped by dependency depth.
Expand Down
36 changes: 21 additions & 15 deletions docs/src/content/docs/integrations/canvas.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,33 +71,38 @@ is not picked up mid-session.
## Trust gate for dependency canvases

A canvas shipped by a **dependency** is arbitrary executable Node.js code. When
the project opts in to the executable gate (by adding `allowExecutables:` to
the project opts in to the executable gate (by adding an `executables:` block to
`apm.yml`), APM blocks dependency-provided canvases unless the package has been
explicitly approved. To deploy them:

```yaml
# apm.yml (committed -- opts the project in to the gate)
allowExecutables: {}
executables: {}
```

```bash
# Run once per developer; approval is stored in ~/.apm/approvals.yml (NOT committed)
# apm approve writes committed project trust (shared with the team);
# add --user to record a personal grant in ~/.apm/config.json instead.
apm approve some-org/canvas-package
apm install --target copilot
```

`apm approve` writes grants to `~/.apm/approvals.yml` -- a user-local file
that is never committed to source control. This means cloning a project with
`allowExecutables: {}` does **not** automatically grant trust to any package;
each developer must explicitly approve packages they want to deploy. For
automated CI pipelines, grants can alternatively be listed directly in
`apm.yml` (committed, shared with the team).
By default `apm approve` writes the grant to the project `apm.yml`
`executables.allow` block (committed), so the trust decision is shared with the
team. `apm approve --user` records a personal grant in `~/.apm/config.json`
instead -- a machine-local override that is never committed. Adding an empty
`executables: {}` enables the gate but grants trust to nothing; approve each
package you want to deploy.

The legacy top-level `allowExecutables:` block is a deprecated alias for
`executables.allow`, read for one minor cycle and migrated on the next
`apm approve` / `apm deny` write.

The trust gate is independent of the experimental flag:

- The **experimental flag** decides whether the canvas primitive is processed at
all. It is a feature-availability gate, not a security gate.
- The **`allowExecutables` block** decides whether *dependency* canvases may
- The **`executables` block** decides whether *dependency* canvases may
deploy. Your own first-party canvas (in the root package you are installing
from) deploys freely once the flag is on; only dependency-provided canvases
need approval.
Expand Down Expand Up @@ -125,7 +130,7 @@ Global canvas install is intentionally limited in this experimental release:
can prune it. A first-party root `.apm/extensions/` canvas is **not** deployed
at user scope -- package it and install it as a dependency instead.
- **Approval is always required.** A global canvas has full-account blast radius,
so `allowExecutables` approval is mandatory even though the project-scope
so executable-trust approval is mandatory even though the project-scope
first-party path does not need it.
- **Default `~/.copilot` only.** If `$COPILOT_HOME` is set to a non-default
location, APM refuses the global canvas install rather than deploy to a path
Expand All @@ -147,13 +152,14 @@ experimental flag, so a previously-installed canvas can always be removed.
(`--target claude`, `cursor`, etc.) never receive it.
- **Global install is dependency-only.** User-scope (`--global`) deployment to
`~/.copilot/extensions/` supports dependency-provided canvases (always
requiring `allowExecutables` approval) and the default `~/.copilot` location
requiring executable-trust approval) and the default `~/.copilot` location
only; first-party root canvases deploy at project scope only.
- **No compile/list surfacing yet.** Canvases are not yet shown by
`apm list`/`apm compile`; they are deployed at install only.
- **No policy-file control yet.** Canvas trust is governed by `allowExecutables`
in `apm.yml`; a dedicated `apm-policy.yml` field for org-wide canvas policy is
planned but not part of this experimental release.
- **No canvas-specific org policy field yet.** The org `executables:` block in
`apm-policy.yml` governs canvas trust alongside the other executable types
(`deny_all`, `deny`, `require`, `recommend`); a canvas-only policy knob is not
planned for this experimental release.

See the [primitives and targets](/apm/concepts/primitives-and-targets/) matrix
for where the canvas primitive sits.
7 changes: 4 additions & 3 deletions docs/src/content/docs/producer/repo-shapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,10 @@ Claude Code skills target. Authoring rules:
**without per-call confirmation**. Treat them as trusted code: keep
them minimal, audited, and free of network side effects you would not
want an agent to trigger unprompted.
- Enterprises can deny deployment per-package or globally via the
`bin_deploy` policy rule -- see the
[policy schema](../../reference/policy-schema/#bin_deploy).
- Enterprises can deny deployment per-package or globally via the org
`executables.deny` policy (the legacy `bin_deploy` rule remains a
deprecated alias) -- see the
[policy schema](../../reference/policy-schema/#executables).

## What to read next

Expand Down
Loading
Loading