Registries
A registry is a REST endpoint that hosts APM packages. Install from a public registry with zero auth, or point at a private / self-hosted registry (Artifactory, JFrog, or any service that implements the Registry HTTP API).
apm experimental enable registriesapm install acme/code-review-prompts#^2.0.0That is the consumer loop.
Compatible backends
Section titled “Compatible backends”| Backend | Status | Notes |
|---|---|---|
| Any server implementing the Registry HTTP API | Supported | Base URL like https://registry.example.com/api/<name>; must expose section 3 endpoints and section 6 publish validation |
| GitHub / Git remotes | Not a registry | Default resolver; use - git: when a default registry is active |
| APM marketplace | Different surface | Git-hosted index via apm pack — not apm publish |
1. Install from a public registry
Section titled “1. Install from a public registry”The simplest case: a hosted public registry that needs no auth. Declare the registry URL in apm.yml, mark it default, and install.
name: my-projectversion: 1.0.0
registries: public-apm: url: https://registry.example.com/api/public-apm default: public-apm
dependencies: apm: - acme/code-review-prompts#^2.0.0apm experimental enable registriesapm installOn success, apm.lock.yaml records source: registry, version, resolved_url, and resolved_hash for each registry dep. A 404 usually means the package is not published or the owner/repo is wrong.
Registry URLs MUST start with https:// (or http:// for local development). Registry names use lowercase letters, digits, -, and .. Unknown keys under a registry entry are rejected at parse time (typo guard).
2. Install from a private or self-hosted registry
Section titled “2. Install from a private or self-hosted registry”Add credentials. Everything from section 1 still applies; the only new step is setting APM_REGISTRY_TOKEN_{NAME}.
name: my-projectversion: 1.0.0
registries: corp-main: url: https://artifactory.corp.example.com/artifactory/api/apm/corp-main-local default: corp-main
dependencies: apm: - acme/code-review-prompts#^2.0.0# Registry name "corp-main" -> APM_REGISTRY_TOKEN_CORP_MAINexport APM_REGISTRY_TOKEN_CORP_MAIN=eyJ...
apm experimental enable registriesapm installA 401 or 403 almost always means the token is missing or misnamed — see Pitfalls.
Workstation-only setup (no registries: block in apm.yml)
Section titled “Workstation-only setup (no registries: block in apm.yml)”When every developer points at the same private registry, configure URL, token, and default locally and keep apm.yml free of a registries: block:
apm experimental enable registriesapm config set registry.corp-main.url \ https://artifactory.corp.example.com/artifactory/api/apm/corp-main-localapm config set registry.corp-main.token eyJ...apm config set registry.corp-main.default true# apm.yml -- dependencies only; no registries: blockname: my-projectversion: 1.0.0
dependencies: apm: - acme/code-review-prompts#^2.0.0Credentials stored in ~/.apm/config.json are user-scoped and never committed to a repository. Only one registry may be default at a time; setting registry.<name>.default true clears any previous default.
Authentication forms
Section titled “Authentication forms”APM reads credentials from environment variables named after the registry. {NAME} is the registry name uppercased, with - and . mapped to _.
| Env var | Auth method |
|---|---|
APM_REGISTRY_TOKEN_{NAME} | Authorization: Bearer <token> |
APM_REGISTRY_USER_{NAME} + APM_REGISTRY_PASS_{NAME} | Authorization: Basic <base64(user:pass)> |
Bearer wins when both forms are set. When neither is set, APM sends the request anonymously and surfaces a remediation hint pointing at APM_REGISTRY_TOKEN_<NAME> on 401 / 403.
# Bearer (preferred for JFrog / Artifactory)export APM_REGISTRY_TOKEN_CORP_MAIN=eyJ...
# HTTP Basic (enterprise registries that issue username/password)export APM_REGISTRY_USER_CORP_MAIN=alice@corp.example.comexport APM_REGISTRY_PASS_CORP_MAIN=secretThe APM_REGISTRY_* prefix is distinct from GITHUB_APM_PAT_*, PROXY_REGISTRY_*, and ARTIFACTORY_APM_TOKEN — there is no collision. For the broader auth model, see Authentication.
Precedence chains
Section titled “Precedence chains”Token precedence (highest wins):
APM_REGISTRY_TOKEN_<NAME>/APM_REGISTRY_USER_<NAME>+APM_REGISTRY_PASS_<NAME>(env vars)registry.<name>.tokenin~/.apm/config.json- Unauthenticated (APM surfaces a remediation hint on
401/403)
Registry URL precedence (highest wins): apm-policy.yml -> project apm.yml -> workspace ~/.apm/apm.yml -> ~/.apm/config.json.
Default registry precedence (highest wins): project apm.yml registries.default -> registry.<name>.default true in ~/.apm/config.json.
apm config reference
Section titled “apm config reference”# Set URL, token, defaultapm config set registry.corp-main.url https://artifactory.corp.example.com/artifactory/api/apm/corp-main-localapm config set registry.corp-main.token eyJ...apm config set registry.corp-main.default true
# Inspectapm config get registry.corp-main.urlapm config get registry.corp-main.tokenapm config get registry.corp-main.default
# Removeapm config unset registry.corp-main.tokenapm config unset registry.corp-main.urlapm config unset registry.corp-main.defaultThese commands are gated behind apm experimental enable registries. apm config set registry.<name>.url is also useful for workspace-level URL overrides (for example, redirecting a registry to a staging server, or reinstalling from a lockfile when the project removed its registries: block).
3. Route dependencies per registry
Section titled “3. Route dependencies per registry”There are two ways to point a dependency at a registry.
String shorthand routed through the default
Section titled “String shorthand routed through the default”When a default registry is configured — via registries.default in apm.yml or registry.<name>.default true in ~/.apm/config.json — plain owner/repo#<ref> shorthand entries route through it:
registries: corp-main: url: https://artifactory.corp.example.com/artifactory/api/apm/corp-main-local default: corp-main
dependencies: apm: - acme/foo#^1.2.3 # semver range -> corp-main - acme/bar#1.4.0 # exact semver -> corp-main - acme/baz#~2.0.0 # tilde range -> corp-mainRegistry-routed deps require a semver version or range. Non-semver refs
(labels like stable/latest, v-prefixed tags such as v1.4.2,
branch names, SHAs) are rejected at parse time when the entry routes to
a registry; use - git: if you need to keep such a ref.
Routing is unconditional: every still-unrouted shorthand entry with a #<ref> is sent through the default registry, regardless of what the ref looks like. Object-form entries (- git:, - path:, - id:) are left alone.
Object form — explicit per-dep routing and virtual packages
Section titled “Object form — explicit per-dep routing and virtual packages”Use the object form to pin a dep to a specific registry, or to install a virtual package (a single file or sub-directory inside a published package):
dependencies: apm: # Whole package via the default registry (registry: omitted) - id: acme/toolkit version: ^2.0.0
# Whole package routed to a specific registry - registry: corp-snapshots id: acme/toolkit version: ^2.0.0
# Virtual package -- one file from inside a published package - registry: corp-main id: acme/prompt-library path: prompts/code-review.prompt.md version: 1.4.0 alias: code-review| Field | Required | Description |
|---|---|---|
id | yes | Package identity at the registry, in owner/repo form. |
version | yes | Exact version or semver range. |
registry | no | Name from the merged registry map. Defaults to the effective default registry when omitted. |
path | no | Sub-path to a file or directory within the published package. Omit to install the whole package. |
alias | no | Local alias (controls install directory name). |
Version selectors
Section titled “Version selectors”Registry-routed entries must specify a semver version or range — the registry uses it to look up an exact match or to range-match against the versions it has published. Non-semver refs (opaque labels, v-prefixed tags, branch names, SHAs) are rejected at parse time:
| Selector | Behavior |
|---|---|
1.0.0, 1.4.2 | Exact semver — matched against the registry catalogue |
^1.0.0, ~1.2.3, >=1.2.0 <2.0.0 | Semver range — APM picks the highest matching version |
stable, latest, v1.4.2, branch/SHA | Rejected — non-semver refs are not allowed for registry-routed deps |
unset (no #<ref>) | Rejected — a version is always required for registry-routed dependencies |
Registry-routed deps are byte-for-byte reproducible via resolved_hash; Git-routed deps are SHA-reproducible via resolved_commit.
Default-routing precedence summary
Section titled “Default-routing precedence summary”| Entry form | Routed to |
|---|---|
owner/repo#<any-ref> | Default registry |
- id: object form (no registry:) | Default registry |
- registry: object form (with registry:) | Named registry |
- git: object form | Git (always — explicit override) |
- path: object form | Local filesystem (unchanged) |
A shorthand entry without any ref (acme/foo) is always rejected — a version selector is required for registry-routed dependencies.
4. What gets recorded in the lockfile
Section titled “4. What gets recorded in the lockfile”Registry-sourced dependencies add four fields to their lockfile entry: source: registry, version, resolved_url, and resolved_hash (sha256 of the archive bytes). The lockfile is promoted to lockfile_version: "2" when any dep is registry-sourced OR carries git-source semver resolution fields (constraint, resolved_tag, or resolved_at — issue #1488). Projects that use neither feature keep lockfile_version: "1" forever, even on a newer client.
dependencies: - repo_url: acme/foo source: registry version: "1.4.0" resolved_url: https://registry.example.com/apm/corp-main/v1/packages/acme/foo/versions/1.4.0/download resolved_hash: "sha256:abc123..." depth: 1 package_type: apm_package deployed_files: - .github/skills/foo/SKILL.mdresolved_url is the trust anchor for re-installs — APM re-fetches from the URL stored in the lockfile, not from the registry name, and re-verifies bytes against resolved_hash. A hash mismatch aborts the install before extraction. See Lockfile spec for full field semantics.
5. Publish a package (producer summary)
Section titled “5. Publish a package (producer summary)”# Producer -- package root with apm.yml, .apm/, and (optionally) a registries: blockapm publish --dry-run -vapm publish
# Consumer -- another repoapm install acme/internal-tools#^1.0.0apm publish reads apm.yml, builds a flat registry archive (.tar.gz with apm.yml and .apm/ at the tarball root), and uploads via PUT /v1/packages/{owner}/{repo}/versions/{version}. Consumers with a default registry configured install with the same owner/repo#version shorthand they would use for GitHub.
Registry archives use the APM source layout that apm install and the Registry HTTP API section 6 expect — not the plugin bundle wrapper from apm pack --archive ({name}-{version}/plugin.json). If you already ship marketplace plugin bundles, either repack as a flat archive or pass --tarball.
Auto-pack requirements:
apm.ymlwithname:andversion:(andsource:when the registry identity differs from the package name)- A
.apm/directory with your primitives (skills, instructions, hooks, etc.)
Auto-pack writes {name}-{version}.tar.gz in the project root and skips macOS ._* / .DS_Store sidecars.
Skill-only or custom layouts — build the tarball yourself and pass --tarball:
tar czf my-skill-0.0.1.tar.gz apm.yml SKILL.mdapm publish --tarball my-skill-0.0.1.tar.gzSome registries accept archives without validating apm.yml on upload; APM still validates on install. Prefer a valid flat layout at publish time.
# Auto-pack flat archive and publish to the only configured registryapm publish
# Choose a registry when multiple are configuredapm publish --registry corp-main
# Publish a pre-built flat tarball (skip auto-pack)apm publish --tarball ./build/my-package-1.0.0.tar.gz
# Preview what would be uploaded without uploadingapm publish --dry-run| Option | Description |
|---|---|
--registry NAME | Registry name from the registries: block. Required when multiple registries are configured. |
--package OWNER/REPO | Override owner/repo identity (default: parsed from source: in apm.yml). |
--tarball PATH | Path to a pre-built flat .tar.gz tarball. Skips auto-pack. |
--dry-run | Preview without uploading. |
--verbose / -v | Show detailed output. |
apm.yml must declare a version: field. Publishing the same version twice returns 409 Conflict — bump the version to publish again.
6. Enterprise policy
Section titled “6. Enterprise policy”Org admins can mandate registry usage and block non-registry sources organization-wide via apm-policy.yml:
registry_source: require: - corp-main # every dep must be reachable via this registry allow_non_registry: false # block any dep not routed through a registryWith allow_non_registry: false, git-sourced dependencies (including shorthand owner/repo entries without a registry route) are blocked at install time. The policy check applies transitively — transitive deps pulled in by registry packages are also validated. APM fails-closed if a listed registry has no URL in the merged registry map (from apm.yml, ~/.apm/apm.yml, or ~/.apm/config.json).
For the full governance narrative — rollout sequencing, audit, drift, and CI gating — see the Governance guide. Field-level reference for registry_source lives in Policy schema.
7. Known limitations and threat model
Section titled “7. Known limitations and threat model”Guarantees
Section titled “Guarantees”- Byte-level reproducibility.
resolved_hashinapm.lock.yamlpins the SHA-256 of the downloaded archive. Re-installs verify bytes against the lockfile hash before writing to disk; a mismatch aborts the install. - Token containment. Tokens stored in
~/.apm/config.jsonare user-scoped and never committed to a repository. - Policy enforcement.
registry_sourceinapm-policy.ymlallows platform teams to mandate and restrict dependency sources across the org.
What this does not yet provide
Section titled “What this does not yet provide”- Package signing. Registry packages are not cryptographically signed. The
resolved_hashdetects corruption or tampering after download, but does not verify publisher identity. - SBOM generation. APM does not produce SLSA provenance attestations or SPDX/CycloneDX bills of materials from registry packages. The lockfile (
apm.lock.yaml) records the resolved version and hash and is suitable for internal audit, but is not a standards-format SBOM. - SHA-256 algorithm agility. The hash floor is SHA-256. No upgrade path to SHA-384/512 is currently implemented.
Do not represent this feature as “supply-chain secure,” “tamper-proof,” or “SLSA-compliant” in compliance documentation or vendor assessments.
Pitfalls
Section titled “Pitfalls”Misspelled env vars look like auth failures
Section titled “Misspelled env vars look like auth failures”When no registry token is found, APM sends the request anonymously first and only prints credential remediation on 401 / 403. A typo in the env var name (for example APM_REGISTRY_TOKEN_CORP_MAI instead of APM_REGISTRY_TOKEN_CORP_MAIN) is treated the same as a missing token — you get a generic auth error, not “unknown env var.”
Verify the exact variable name:
# Registry name corp-main -> APM_REGISTRY_TOKEN_CORP_MAINecho "$APM_REGISTRY_TOKEN_CORP_MAIN" | wc -c # should be > 1apm config get registry.corp-main.token # config.json fallbackUse apm config set registry.<name>.token when debugging locally so a missing export does not masquerade as a server-side permission problem.
Default registry silently reroutes Git shorthand
Section titled “Default registry silently reroutes Git shorthand”Enabling a default registry (registries.default or registry.<name>.default true) routes every owner/repo#ref shorthand to the registry — including deps that previously installed from GitHub. There is no migration warning on apm install; the first signal is often a registry 404 (“no versions”) instead of a git clone.
Before turning on a default registry:
-
Audit
apm.yml(and transitive packages) for shorthand deps that must stay on Git. -
Pin those entries explicitly:
dependencies:apm:- git: https://github.com/microsoft/apm-sample-package.gitref: v1.0.0 -
Run
apm install --dry-runor a trial install in a branch and confirm lockfilesource:fields.
See Migration paths — default registry adoption.
Registry names that sanitize to the same env var
Section titled “Registry names that sanitize to the same env var”Env var names derive from the registry name by uppercasing and mapping - and . to _. Distinct registry names can collapse to the same env var:
Registry names in apm.yml | Shared env var |
|---|---|
corp-main | APM_REGISTRY_TOKEN_CORP_MAIN |
corp.main | APM_REGISTRY_TOKEN_CORP_MAIN |
Corp-Main | APM_REGISTRY_TOKEN_CORP_MAIN |
Do not configure two different registries whose names sanitize identically — they would share one token slot. Prefer hyphenated names (corp-main) and avoid dots in registry names when multiple registries coexist.
Full example
Section titled “Full example”name: my-projectversion: 1.0.0
registries: corp-main: url: https://artifactory.corp.example.com/artifactory/api/apm/corp-main-local default: corp-main
dependencies: apm: # String shorthand -> corp-main (semver range) - acme/code-review-prompts#^2.0.0
# Object form, whole package, explicit registry - registry: corp-main id: acme/security-baseline version: ~1.4.0
# Object form, virtual package - registry: corp-main id: acme/prompt-library path: prompts/code-review.prompt.md version: 1.4.0registry_source: require: - corp-main allow_non_registry: false# Developer workstation setup (config-only registry)apm experimental enable registriesapm config set registry.corp-main.url https://artifactory.corp.example.com/artifactory/api/apm/corp-main-localapm config set registry.corp-main.token "$(cat ~/.corp-apm-token)"apm config set registry.corp-main.default trueapm installSee also
Section titled “See also”- Manifest schema — formal grammar for the
registries:block and- id:object form. - Lockfile spec — lockfile schema and registry-specific fields.
- Authentication — full token-resolution chain.
- apm config — full config key reference.
- Policy schema —
registry_sourcefield reference. - Governance guide — enterprise rollout, audit, and CI gating.
- Security model — threat model and known limitations.
- Registry HTTP API — wire contract for registry servers.