Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8243134
feat: support allow-insecure HTTP dependencies (review item 14 stack …
arika0093 Apr 21, 2026
5128b5b
fix: address Sergio review on insecure HTTP dependencies (review item…
arika0093 Apr 21, 2026
3a49c79
feat: tighten insecure HTTP dependency safeguards (review item 14 sta…
arika0093 Apr 21, 2026
30353b9
feat: gate transitive insecure dependencies by host (review item 14 s…
arika0093 Apr 20, 2026
036c599
feat: refine HTTP dependency transport and safeguards (review item 14…
arika0093 Apr 21, 2026
333e8f0
refactor(config): share config command boolean helpers (review item 1…
arika0093 Apr 21, 2026
ea45c9b
feat: refine HTTP clone and validation transport behavior (review ite…
arika0093 Apr 21, 2026
b5ce27e
fix(security): suppress credential helpers on HTTP git attempts (revi…
arika0093 Apr 21, 2026
c67cfde
fix(drift): treat HTTP transport flips as dependency drift (review it…
arika0093 Apr 21, 2026
9032b9e
refactor(install): extract insecure dependency policy module (review …
arika0093 Apr 21, 2026
1fca10b
fix(lockfile): replay allow_insecure for HTTP dependencies (review it…
arika0093 Apr 21, 2026
caeb13d
fix(reference): keep canonical dependency strings scheme-free (review…
arika0093 Apr 21, 2026
85be6d1
refactor(lockfile): centralize locked dependency reference mapping (r…
arika0093 Apr 21, 2026
f08a716
fix(install): unify HTTP dependency remediation errors (review item 7)
arika0093 Apr 21, 2026
17e40a3
docs(security): document HTTP dependency threat model (review item 8)
arika0093 Apr 21, 2026
32cb21f
refactor(install): require logger for insecure policy diagnostics (re…
arika0093 Apr 21, 2026
b067ab7
refactor(install): raise policy errors instead of exiting (review ite…
arika0093 Apr 21, 2026
2dd90af
fix(install): tighten insecure HTTP warning copy (review item 11)
arika0093 Apr 21, 2026
783450f
fix(install): shorten transitive HTTP remediation error (review item 12)
arika0093 Apr 21, 2026
b8a5c5f
fix(deps): rename insecure list origin column (review item 13)
arika0093 Apr 21, 2026
c8bcc70
merge: resolve conflicts with main + extract MCP warning helpers
danielmeppiel Apr 21, 2026
35d6463
merge: resolve conflicts with main after PR #809 landed
danielmeppiel Apr 21, 2026
2c7e81b
review: address panel round-2 nits (N1-N5, F2, F3)
danielmeppiel Apr 21, 2026
fe7355f
review: fix CodeQL "Incomplete URL substring sanitization" finding
danielmeppiel Apr 21, 2026
ea69092
Merge branch 'main' into feat/allow-http-request
danielmeppiel Apr 21, 2026
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `apm install --mcp NAME [--transport ...] [--url ...] [--env K=V] [--header K=V] [--mcp-version V] [-- COMMAND ARGS...]` flag for declaratively adding MCP servers to `apm.yml`. Mirrors `apm install` for packages: validates input through `MCPDependency`, writes to `dependencies.mcp` (or `devDependencies.mcp` with `--dev`), and integrates the new server into client adapters. Idempotency policy: in a TTY, prompts before replacing an existing entry; in CI, requires `--force`. Also accessible via `apm mcp install` alias for discoverability. Closes #807 (#810)
- `apm install --mcp NAME --registry URL` flag for resolving registry-form MCP servers against a custom (e.g. private/enterprise) MCP registry. Precedence chain: CLI flag > `MCP_REGISTRY_URL` env var > default (`https://api.mcp.github.com`). The URL is validated (`http`/`https` only; `ws://`, `file://`, `javascript:`, schemeless and >2048-char values rejected with usage errors), captured in `apm.yml` on the entry's `registry:` field for auditability, and applied to the install session via the registry resolver. Both `http://` and `https://` are accepted via the CLI flag (explicit per-invocation user intent); the env-var path keeps the stricter `https`-by-default policy with `MCP_REGISTRY_ALLOW_HTTP=1` opt-in. Forwards transparently through the `apm mcp install` alias. Conflicts with `--url` or a stdio command (self-defined entries do not consult a registry). Per-project default via `apm config set mcp-registry-url` is tracked as a follow-up (#818). (#810)
- New **MCP Servers** guide (`docs/guides/mcp-servers.md`) consolidating the `apm install --mcp` workflow: stdio / registry / remote shapes, full flag reference, validation rules, and the curated conflict matrix in one page (#808). Sidebar entry added under Guides. Documents the `--mcp` / `--transport` / `--url` / `--env` / `--header` / `--mcp-version` flag family and the `apm mcp install` alias in `reference/cli-commands.md`. Documents the `MCP_REGISTRY_URL` environment variable for pointing `apm install --mcp` at a custom MCP registry (enterprise). Drift fixes in the same PR: removes the stale "Phase 2 - Coming Soon" MCP section in `guides/prompts.md`, fixes a broken `apm mcp info` reference in `integrations/ide-tool-integration.md`, replaces an emoji compatibility table with ASCII, and aligns MCP registry-name examples on the canonical `io.github.github/...` form across `key-concepts.md` and `ide-tool-integration.md`.
- `apm deps list --insecure` now filters installed dependencies locked to `http://` sources and shows whether each one is direct or transitive (#700)
- `apm install --ssh` / `--https` flags and `APM_GIT_PROTOCOL=ssh|https` env to pick the initial transport for shorthand dependencies (#778)
- `apm install --allow-protocol-fallback` flag and `APM_ALLOW_PROTOCOL_FALLBACK=1` env as the migration escape hatch for cross-protocol fallback (#778)
- Add APM Review Panel skill (`.github/skills/apm-review-panel/`) and four new specialist personas (`devx-ux-expert`, `supply-chain-security-expert`, `apm-ceo`, `oss-growth-hacker`) with auto-activating per-persona skills. Routes specialist findings through an APM CEO arbiter for strategic / breaking-change calls, with the OSS growth hacker side-channeling adoption insights via `WIP/growth-strategy.md`. Instrumentation per Handbook Ch. 9 (`The Instrumented Codebase`); PROSE-compliant (thin SKILL.md routers, persona detail lazy-loaded via markdown links, explicit boundaries per persona).
Expand All @@ -34,6 +35,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Multi-target support: `apm.yml` `target` field now accepts a list (`target: [claude, copilot]`) and CLI `--target` accepts comma-separated values (`-t claude,copilot`). Only specified targets are compiled, installed, and packed -- no redundant output for unused tools. Single-string syntax is fully backward compatible. (#628)

### Changed

- HTTP dependency installs now require per-run `--allow-insecure`, warn on every insecure fetch, automatically allow same-host transitive `http://` sources, and use `--allow-insecure-host` for additional transitive hosts (#700)

### Fixed

- VS Code adapter now defaults to `http` transport when `transport_type` is missing from remote registry data, matching Copilot adapter behavior (#654)
Expand Down
20 changes: 20 additions & 0 deletions docs/src/content/docs/enterprise/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ The `resolved_commit` field is a full 40-character SHA, not a branch name or tag

APM does not use a package registry. Dependencies are specified as git repository URLs in `apm.yml`. This eliminates the registry compromise vector entirely — there is no centralized service that can be poisoned to redirect installs.

### HTTP (insecure) dependencies

APM supports `http://` git dependencies for private mirrors and air-gapped
environments, but only behind explicit approval on both the manifest and CLI
surfaces:

- `allow_insecure: true` on the dependency entry records that the project
intentionally permits HTTP for that dependency.
- `apm install --allow-insecure` approves direct HTTP dependencies for the
current install run.
- Transitive HTTP dependencies inherit approval only when they come from the
same host as an approved direct HTTP dependency. Additional transitive hosts
require `--allow-insecure-host HOSTNAME`.

These controls make the decision visible, but they do **not** make HTTP safe:

- HTTP has no transport encryption or server authentication. A machine-in-the-middle can modify repository contents or refs in transit.
- On the first HTTP fetch (or any update fetched over HTTP), the lockfile's `resolved_commit` and `content_hash` come from that same untrusted channel. They improve replay detection later, but they do not establish trustworthy provenance for the initial fetch.
- APM explicitly suppresses git credential helpers for HTTP clone and `ls-remote` operations so stored tokens from Keychain, Credential Manager, `gh auth`, or other helpers are not sent over plaintext HTTP.

## Content scanning

### The threat
Expand Down
22 changes: 20 additions & 2 deletions docs/src/content/docs/guides/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,22 @@ dependencies:
ref: v1.0
```

Fields: `git` (required), `path`, `ref`, `alias` (all optional). The `git` value is any HTTPS or SSH clone URL. Explicit URL schemes are honored exactly -- see [Transport selection](#transport-selection-ssh-vs-https) for the full contract. Custom ports are preserved across every attempt (including any cross-protocol fallback enabled with `--allow-protocol-fallback`), so `ssh://host:7999/...` retried over HTTPS becomes `https://host:7999/...`.
Fields: `git` (required), `path`, `ref`, `alias` (all optional). The `git` value is any HTTPS, HTTP or SSH clone URL.

Explicit URL schemes are honored exactly -- see [Transport selection](#transport-selection-ssh-vs-https) for the full contract. Custom ports are preserved across every attempt (including any cross-protocol fallback enabled with `--allow-protocol-fallback`), so `ssh://host:7999/...` retried over HTTPS becomes `https://host:7999/...`.

:::caution
Use HTTP dependencies only on trusted private networks. Declare them with
`git: http://...` and `allow_insecure: true` in `apm.yml`. Installing them
still requires `apm install --allow-insecure`.

HTTP has no transport authentication, so anyone who can intercept the
connection can swap the package contents in transit. APM warns on every
`http://` fetch, allows same-host transitive HTTP dependencies when you
already passed `--allow-insecure` for a direct HTTP dependency on that host,
and otherwise requires `--allow-insecure-host <hostname>` for each additional
transitive host you want to allow.
:::

> **Nested groups (GitLab, Gitea, etc.):** APM treats all path segments after the host as the repo path, so `gitlab.com/group/subgroup/repo` resolves to a repo at `group/subgroup/repo`. Virtual paths on simple 2-segment repos work with shorthand (`gitlab.com/owner/repo/file.prompt.md`). But for **nested-group repos + virtual paths**, use the object format — the shorthand is ambiguous:
>
Expand Down Expand Up @@ -216,6 +231,9 @@ apm install --dry-run
# List installed packages
apm deps list

# Show only installed HTTP-backed packages
apm deps list --insecure

# Show dependency tree
apm deps tree

Expand Down Expand Up @@ -446,7 +464,7 @@ migrating CI), set `APM_ALLOW_PROTOCOL_FALLBACK=1` or pass
| Dependency form | What APM tries |
|-----------------|----------------|
| `ssh://...` or `git@host:...` | SSH only |
| `https://...` or `http://...` | HTTPS only |
| `https://...` or `http://...` | HTTP(S) only |
| Shorthand (`owner/repo`, `host/owner/repo`) with `git config url.<base>.insteadOf` rewriting to SSH | SSH only |
| Shorthand without a matching `insteadOf` rewrite | HTTPS only |

Expand Down
28 changes: 23 additions & 5 deletions docs/src/content/docs/reference/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ apm init my-plugin --plugin

### `apm install` - Install dependencies and deploy local content

Install APM package and MCP server dependencies from `apm.yml` and deploy the project's own `.apm/` content to target directories (like `npm install`). Auto-creates minimal `apm.yml` when packages are specified but no manifest exists.
Install APM package and MCP server dependencies from `apm.yml` and deploy the project's own `.apm/` content to target directories (like `npm install`). Auto-creates minimal `apm.yml` when packages are specified but no manifest exists. For `http://` dependencies, use `--allow-insecure`.

```bash
apm install [PACKAGES...] [OPTIONS]
Expand Down Expand Up @@ -103,6 +103,8 @@ apm install [PACKAGES...] [OPTIONS]
- `--registry URL` - Custom MCP registry URL (`http://` or `https://`) for resolving the registry-form `--mcp NAME`. Overrides `MCP_REGISTRY_URL`. Persisted to `apm.yml` for reproducible installs. Not valid with `--url` or a stdio command. Only with `--mcp`.
- `--dev` - Add packages to [`devDependencies`](../manifest-schema/#5-devdependencies) instead of `dependencies`. Dev deps are installed locally but excluded from `apm pack --format plugin` bundles
- `-g, --global` - Install to user scope (`~/.apm/`) instead of the current project. Primitives deploy to `~/.copilot/`, `~/.claude/`, etc. MCP servers are only installed for global-capable runtimes (Copilot CLI, Codex CLI); workspace-only runtimes are skipped.
- `--allow-insecure` - Allow HTTP (insecure) dependencies. Required when adding or installing dependencies that use an `http://` URL.
- `--allow-insecure-host HOSTNAME` - Allow transitive HTTP (insecure) dependencies from `HOSTNAME`. Repeat the flag to allow multiple hosts.
- `--ssh` - Force SSH for shorthand (`owner/repo`) dependencies. Mutually exclusive with `--https`. Ignored for URLs with an explicit scheme.
- `--https` - Force HTTPS for shorthand dependencies. Mutually exclusive with `--ssh`. Default unless `git config url.<base>.insteadOf` rewrites the candidate to SSH.
- `--allow-protocol-fallback` - Restore the legacy permissive cross-protocol fallback chain (HTTPS-then-SSH or vice-versa). Strict-by-default otherwise. Each retry emits a `[!]` warning naming both protocols. When the dependency URL carries a custom port, APM also emits a one-shot `[!]` warning before the first clone attempt noting that the same port will be reused across schemes (wrong on servers like Bitbucket Datacenter that serve SSH and HTTPS on different ports) -- to avoid the mismatch, omit this flag and pin the dependency with an explicit `ssh://` or `https://` URL.
Expand All @@ -119,6 +121,8 @@ See [Dependencies: Transport selection](../../guides/dependencies/#transport-sel
**Behavior:**
- `apm install` (no args): Installs **all** packages from `apm.yml` and deploys the project's own `.apm/` content
- `apm install <package>`: Installs **only** the specified package (adds to `apm.yml` if not present)
- Each `http://` dependency is warned at install time before any fetch begins
- Transitive `http://` dependencies are allowed automatically when they use the same host as a direct insecure dependency you approved with `--allow-insecure`; other transitive hosts require `--allow-insecure-host HOSTNAME`

**Local `.apm/` Content Deployment:**

Expand Down Expand Up @@ -753,6 +757,7 @@ apm deps list [OPTIONS]
**Options:**
- `-g, --global` - List user-scope packages from `~/.apm/` instead of the current project
- `--all` - List packages from both project and user scope
- `--insecure` - Show only installed dependencies locked to `http://` sources

**Examples:**
```bash
Expand All @@ -764,6 +769,9 @@ apm deps list -g

# Show both scopes
apm deps list --all

# Show only insecure installed dependencies
apm deps list --insecure
```

**Sample Output:**
Expand All @@ -776,6 +784,20 @@ apm deps list --all
└─────────────────────┴─────────┴──────────┴─────────┴──────────────┴────────┴────────┘
```

With `--insecure`, an additional `Origin` column (rendered bold red) sits
between `Source` and `Prompts`. Values are `direct` for HTTP deps declared
in `apm.yml` and `via <parent>` for transitive HTTP deps pulled in by
another package:

```
┌─────────────────┬─────────┬──────────┬────────────────┬─────────┬──────────────┬────────┬────────┐
│ Package │ Version │ Source │ Origin │ Prompts │ Instructions │ Agents │ Skills │
├─────────────────┼─────────┼──────────┼────────────────┼─────────┼──────────────┼────────┼────────┤
│ internal-pkg │ 1.0.0 │ github │ direct │ 1 │ - │ - │ - │
│ shared-rules │ 2.0.0 │ github │ via acme/pkg │ - │ 1 │ - │ - │
└─────────────────┴─────────┴──────────┴────────────────┴─────────┴──────────────┴────────┴────────┘
```

**Output includes:**
- Package name and version
- Source information
Expand Down Expand Up @@ -1514,10 +1536,6 @@ apm config set auto-integrate true

# Disable auto-integration
apm config set auto-integrate false

# Using alternative boolean values
apm config set auto-integrate yes
apm config set auto-integrate 1
```

**`temp-dir`** - Override the system temporary directory
Expand Down
2 changes: 2 additions & 0 deletions docs/src/content/docs/reference/lockfile-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ fields:
| `deployed_files` | array of strings | MUST | Every file path APM deployed for this dependency, relative to project root. |
| `source` | string | MAY | Dependency source. `"local"` for local path dependencies. Omitted for remote (git) dependencies. |
| `local_path` | string | MAY | Filesystem path (relative or absolute) to the local package. Present only when `source` is `"local"`. |
| `is_insecure` | boolean | MAY | `true` when the dep was fetched over HTTP (unencrypted). Omitted when `false`. Presence forces re-approval on the next install: the apm.yml entry MUST carry `allow_insecure: true` and the invocation MUST pass `--allow-insecure` (or `--allow-insecure-host` for transitive deps). Absent or `false` means HTTPS/SSH. |
| `allow_insecure` | boolean | MAY | `true` when the user's manifest explicitly approved the HTTP fetch with `allow_insecure: true`. Persisted alongside `is_insecure` for replay safety: a legacy lockfile with `is_insecure: true` but no `allow_insecure` fail-closes to `allow_insecure: false`, forcing re-approval. Omitted when `false`. |

Fields with empty or default values (empty strings, `false` booleans, empty
lists) SHOULD be omitted from the serialized output to keep the file concise.
Expand Down
4 changes: 2 additions & 2 deletions packages/apm-guide/.apm/skills/apm-usage/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@

| Command | Purpose | Key flags |
|---------|---------|-----------|
| `apm install [PKGS...]` | Install packages | `--update` refresh refs, `--force` overwrite, `--dry-run`, `--verbose`, `--only [apm\|mcp]`, `--target` (comma-separated), `--dev`, `-g` global, `--trust-transitive-mcp`, `--parallel-downloads N`, `--mcp NAME` add MCP entry, `--transport`, `--url`, `--env KEY=VAL`, `--header KEY=VAL`, `--mcp-version`, `--registry URL` custom MCP registry |
| `apm install [PKGS...]` | Install packages | `--update` refresh refs, `--force` overwrite, `--dry-run`, `--verbose`, `--only [apm\|mcp]`, `--target` (comma-separated), `--dev`, `-g` global, `--trust-transitive-mcp`, `--parallel-downloads N`, `--allow-insecure`, `--allow-insecure-host HOSTNAME`, `--mcp NAME` add MCP entry, `--transport`, `--url`, `--env KEY=VAL`, `--header KEY=VAL`, `--mcp-version`, `--registry URL` custom MCP registry |
| `apm uninstall PKGS...` | Remove packages | `--dry-run`, `-g` global |
| `apm prune` | Remove orphaned packages | `--dry-run` |
| `apm deps list` | List installed packages | `-g` global, `--all` both scopes |
| `apm deps list` | List installed packages | `-g` global, `--all` both scopes, `--insecure` |
| `apm deps tree` | Show dependency tree | -- |
| `apm view PKG [FIELD]` | View package details or remote refs | `-g` global, `FIELD=versions` |
| `apm outdated` | Check locked deps via SHA/semver comparison | `-g` global, `-v` verbose, `-j N` parallel checks |
Expand Down
34 changes: 33 additions & 1 deletion packages/apm-guide/.apm/skills/apm-usage/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ across protocols.
| Dependency form | What APM tries |
|-----------------|----------------|
| `ssh://...` or `git@host:...` | SSH only |
| `https://...` or `http://...` | HTTPS only |
| `https://...` or `http://...` | HTTP(S) only |
| Shorthand with `git config url.<base>.insteadOf` rewriting to SSH | SSH only |
| Shorthand otherwise | HTTPS only |

Expand Down Expand Up @@ -194,6 +194,38 @@ When installing from a marketplace, the `#` suffix overrides the `source.ref` fr
| `plugin@mkt#main` | Override with branch | `plugin@mkt#main` |
| `plugin@mkt#abc123d` | Override with commit SHA | `plugin@mkt#abc123d` |

## HTTP dependencies (opt-in)

HTTP is never attempted implicitly. A dep fetched over `http://` requires
dual opt-in on every install:

1. **Manifest approval** -- the apm.yml entry carries `allow_insecure: true`.
2. **Invocation approval** -- `apm install --allow-insecure` for direct
deps, or `--allow-insecure-host HOSTNAME` (repeatable) for transitive
deps. Transitive HTTP deps from hosts not listed are blocked.

Example apm.yml entry:

```yaml
dependencies:
apm:
- git: http://mirror.example.com/acme/rules.git
ref: v1.2.0
allow_insecure: true
```

Example invocation:

```bash
apm install --allow-insecure --allow-insecure-host mirror.example.com
```

Mental model: HTTP is opt-in per-dep AND per-invocation. Removing either
side re-locks the dependency. The lockfile records `is_insecure: true` and
`allow_insecure: true` on the entry so replays fail-closed when either
approval is dropped. See `commands.md` for full flag syntax and the
enterprise security guide for the threat model.

## What the lockfile pins

`apm.lock.yaml` records the exact commit SHA for every dependency, regardless
Expand Down
Loading
Loading