Skip to content

Commit 43f8f78

Browse files
Merge branch 'main' into fix/816-mcp-host-validation
2 parents 88ebd09 + 906d15e commit 43f8f78

67 files changed

Lines changed: 6995 additions & 649 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
run: |
5555
# Ruff has no max-module-lines rule. This check prevents new files from
5656
# exceeding the current worst case. Tighten the threshold over time.
57-
MAX_LINES=2400 # current max: 2316 (github_downloader.py)
57+
MAX_LINES=2450 # current max: 2404 (github_downloader.py)
5858
VIOLATIONS=$(find src/ -name '*.py' -print0 | xargs -0 -I{} awk -v max="$MAX_LINES" \
5959
'END { if (NR > max) printf "%s: %d lines (max %d)\n", FILENAME, NR, max }' {})
6060
if [ -n "$VIOLATIONS" ]; then

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Virtual subdirectory and raw-file packages now resolve from self-hosted Git services (Gitea, Gogs) via raw URL with API v1/v3 fallback. (#587)
1313
- `shared/apm.md` gh-aw shared workflow exposes a `target:` import input (default `all`) so consumer workflows can ship slim, single-harness bundles instead of always packing every layout. (#1184)
14+
- **GitLab host support:** `gitlab.com` and self-managed instances (via `GITLAB_HOST` / `APM_GITLAB_HOSTS`) use GitLab REST **v4** for `marketplace.json` and install-time raw file reads; nested GitLab group paths are disambiguated in dependency references with object-form `git:` + `path:` where shorthand is ambiguous. GitHub, GHES, Azure DevOps, and registry-proxy behavior remain unchanged. (#1149)
15+
- **`git: parent` monorepo transitive dependency inheritance:** packages in a git monorepo can reference sibling paths via `{ git: parent, path: ... }` without repeating the full `git:` URL; the lockfile stores expanded host, repository, subdirectory path, and resolved ref/commit like other virtual git dependencies (no `parent` sentinel as durable identity). (#1149)
1416
- If you use the `gh` CLI, APM is now zero-config for private GitHub packages on github.com, `*.ghe.com`, and GHES: APM uses your active `gh auth login` token (`gh auth token --hostname <host>`) before falling back to `git credential fill`. Silently skipped when `gh` is not installed or not logged in for the host. (#630)
1517

18+
### Changed
19+
20+
- `apm marketplace browse/search/add/update` route through the registry proxy when `PROXY_REGISTRY_URL` is set; `PROXY_REGISTRY_ONLY=1` blocks direct GitHub and GitLab host API fallbacks. (#1149)
21+
- Registry proxy now warns when `PROXY_REGISTRY_TOKEN` is set and `PROXY_REGISTRY_URL` uses `http://`, since the bearer token would be transmitted in plaintext; set `PROXY_REGISTRY_ALLOW_HTTP=1` to silence the warning for trusted internal proxies. (#1149)
22+
1623
### Fixed
1724

25+
- `apm marketplace add` accepts GitLab-class hosts (`gitlab.com` and self-managed instances configured via `GITLAB_HOST` / `APM_GITLAB_HOSTS`); unsupported generic hosts now show separate recovery hints for GHES (`GITHUB_HOST`) and self-managed GitLab instead of only `GITHUB_HOST`. (#1149)
26+
- **GitLab monorepo marketplaces:** `apm install plugin@marketplace` now resolves plugins whose sources live in a subdirectory of the marketplace repository on GitLab-class hosts (`gitlab.com` and self-managed GitLab when classified as GitLab), matching explicit `git:` + `path:` semantics without requiring that hand-written object form. (#1149)
27+
- `apm install` now rejects unsupported flat-format `dependencies` (e.g. `dependencies: [owner/repo]`) with a clear error and structured-format hint instead of silently ignoring them; the resolver also surfaces `ValueError` from malformed transitive manifests as warnings instead of swallowing them. (#1189)
1828
- `shared/apm.md` no longer wraps the `target` input in a `|| 'all'` fallback. The defensive expression broke gh-aw's bare-expression substitution regex, causing consumer-supplied `target:` values to be silently dropped; the `import-schema` default already covers the omitted-input case. (#1185)
1929
- `apm install --target all` no longer enumerates the experimental `copilot-cowork` target, which was crashing project-scope installs with a "requires --global" error and made `gh aw` workflows that pin `target: all` unusable. (#1191)
2030
- Stabilized `test_install_over_defer_threshold_starts_live_once` on slow CI runners by joining the deferred-start timer thread instead of relying on a 100ms grace window. (#1191)
@@ -24,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2434
- `apm install --update` now falls back from a stale `ADO_APM_PAT` to an `az login` AAD bearer in the preflight auth probe, matching the behavior of `apm install` and every other ADO call site. Previously the preflight raised `AuthenticationError` on 401/403 even when `az login` would have succeeded. The bearer env also pops any pre-existing `GIT_TOKEN` so the JWT flows only via `GIT_CONFIG_VALUE_0`, and the per-host stale-PAT warning dedup is lock-guarded so parallel installs against the same ADO host emit one warning instead of one-per-thread. (#1212)
2535
- `Unknown target` error suggestions no longer advertise the `agent-skills` meta-target, which `apm targets` intentionally omits from its table. The canonical set still accepts `agent-skills` via `--target` and `apm.yml`, but the recovery path printed on errors now matches what the discovery command actually lists. (#1215)
2636
- `apm pack` no longer hardcodes `pack.target` into bundles; bundles are target-agnostic and `apm install <bundle>` resolves the consumer target from project context and wires bundle `.mcp.json` servers per target via `MCPIntegrator`. (#1217)
37+
- Multi-account Git Credential Manager users: APM now selects the right GitHub account automatically per repository (no account-picker prompt) when `credential.useHttpPath = true` is set. Existing single-account setups are unaffected. (#1226)
2738

2839
## [0.12.4] - 2026-05-07
2940

docs/src/content/docs/getting-started/authentication.md

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,54 @@ sidebar:
44
order: 4
55
---
66

7-
APM works without tokens for public packages on github.com. Authentication is needed for private repositories, enterprise hosts (`*.ghe.com`, GHES), and Azure DevOps.
7+
APM works without tokens for public packages on github.com. Authentication is needed for private repositories, enterprise hosts (`*.ghe.com`, GHES), GitLab (private or API access), and Azure DevOps.
88

99
## How APM resolves authentication
1010

11-
APM resolves tokens per `(host, org)` pair. For each dependency, it walks a resolution chain until it finds a token:
11+
APM resolves tokens per `(host, port, org)` pair. For each dependency, it walks a **host-class-specific** chain until it finds a token:
1212

13-
1. **Per-org env var**`GITHUB_APM_PAT_{ORG}` (GitHub-like hosts — not ADO)
14-
2. **Global env vars**`GITHUB_APM_PAT``GITHUB_TOKEN``GH_TOKEN` (any host)
15-
3. **GitHub CLI active account**`gh auth token --hostname <host>` (GitHub-like hosts; silently skipped if `gh` is not installed or not logged in)
16-
4. **Git credential helper**`git credential fill` (any host except ADO)
13+
1. **GitHub-class hosts** (`github.com`, `*.ghe.com`, GHES via `GITHUB_HOST`): **Per-org env var** `GITHUB_APM_PAT_{ORG}` (when an org slug applies), then **global** `GITHUB_APM_PAT` -> `GITHUB_TOKEN` -> `GH_TOKEN`, then **GitHub CLI active account** (`gh auth token --hostname <host>`, silently skipped if `gh` is not installed or not logged in for the host), then host-specific **git credential helper**.
14+
2. **GitLab-class hosts** (`gitlab.com`, or FQDNs listed via `GITLAB_HOST` / `APM_GITLAB_HOSTS`): **only** `GITLAB_APM_PAT` -> `GITLAB_TOKEN`, then host-specific **git credential helper**. GitHub token env vars are **not** used for GitLab (including `GITHUB_APM_PAT`, `GITHUB_TOKEN`, and `GH_TOKEN`, and `GITHUB_APM_PAT_{ORG}` for group/namespace paths).
15+
3. **Generic hosts** (other FQDNs such as Bitbucket): host-specific **git credential helper** or unauthenticated/public access -- **no** GitHub or GitLab platform env vars.
1716

18-
Steps 1 and 2 cover the four token-priority rows in the table below (priorities 1-4). The numbering above collapses the three global env vars (`GITHUB_APM_PAT`, `GITHUB_TOKEN`, `GH_TOKEN`) into a single resolution step.
17+
Azure DevOps uses its own chain (`ADO_APM_PAT` -> Azure CLI bearer). See [Azure DevOps](#azure-devops).
18+
If the resolved token fails for the target host, APM retries with git credential helpers on paths that support it. If nothing matches, APM attempts unauthenticated access where the host exposes public repos (not *ghe.com* Data Residency).
1919

20-
If the global token doesn't work for the target host, APM next tries the active `gh` CLI account before falling back to git credential helpers. If nothing matches, APM attempts unauthenticated access (works for public repos on github.com).
21-
22-
Results are cached per-process — the same `(host, org)` pair is resolved once.
23-
24-
All token-bearing requests use HTTPS. Tokens are never sent over unencrypted connections.
25-
26-
`apm install <package>` validation walks the same chain as the actual install: an authenticated attempt with the resolved token first, then a credential-helper fallback (plain HTTPS where the system credential helper provides the token). This means `apm install` from the CLI never rejects a package the lockfile-driven install would accept -- useful when an env-var PAT has narrower SSO/EMU access than the token your `gh auth setup-git` / OS keychain has cached.
20+
Results are cached per-process for each `(host, port, org)` key. All token-bearing requests use HTTPS.
2721

2822
## Token lookup
23+
### GitHub-class hosts (`github.com`, `*.ghe.com`, GHES via `GITHUB_HOST`)
2924

3025
| Priority | Variable | Scope | Notes |
3126
|----------|----------|-------|-------|
32-
| 1 | `GITHUB_APM_PAT_{ORG}` | Per-org, GitHub-like hosts | Org name uppercased, hyphens → underscores |
33-
| 2 | `GITHUB_APM_PAT` | Any host | Falls back to git credential helpers if rejected |
34-
| 3 | `GITHUB_TOKEN` | Any host | Shared with GitHub Actions |
35-
| 4 | `GH_TOKEN` | Any host | Set by `gh auth login` |
36-
| 5 | `gh auth token --hostname <host>` | GitHub-like hosts | Active `gh auth login` account |
37-
| 6 | `git credential fill` | Per-host | System credential manager, `gh auth`, OS keychain |
27+
| 1 | `GITHUB_APM_PAT_{ORG}` | Per-org | Org name uppercased, hyphens -> underscores |
28+
| 2 | `GITHUB_APM_PAT` | Global | Falls back to git credential helpers if rejected |
29+
| 3 | `GITHUB_TOKEN` | Global | Often set in GitHub Actions |
30+
| 4 | `GH_TOKEN` | Global | Often set by `gh auth login` |
31+
| 5 | `gh auth token --hostname <host>` | Per-host | Active `gh auth login` account; silently skipped if `gh` is missing or not logged in |
32+
| 6 | `git credential fill` | Per-host | System credential manager, OS keychain |
33+
34+
### GitLab-class hosts (`gitlab.com`, `GITLAB_HOST`, `APM_GITLAB_HOSTS`)
35+
36+
| Priority | Variable | Notes |
37+
|----------|----------|-------|
38+
| 1 | `GITLAB_APM_PAT` | Preferred dedicated variable for APM + GitLab |
39+
| 2 | `GITLAB_TOKEN` | CI / automation-friendly name (`CI_JOB_TOKEN`, etc.) |
40+
| 3 | `git credential fill` | Host-scoped HTTPS credentials |
3841

39-
For Azure DevOps, APM resolves credentials in this order: `ADO_APM_PAT` env var, then a Microsoft Entra ID (AAD) bearer token from the Azure CLI (`az`). See [Azure DevOps](#azure-devops) below.
42+
**GitLab exclusion:** GitHub PAT env vars (`GITHUB_APM_PAT`, `GITHUB_APM_PAT_{ORG}`, `GITHUB_TOKEN`, `GH_TOKEN`) are **never** chosen for GitLab-class hosts — even if set — because they commonly appear in unrelated contexts (for example Actions) and must not be sent to GitLab as `PRIVATE-TOKEN` or HTTPS credentials.
4043

41-
For Artifactory registry proxies, use `PROXY_REGISTRY_TOKEN`. See [Registry proxy (Artifactory)](#registry-proxy-artifactory) below.
44+
### Generic hosts (e.g. Bitbucket, self-hosted SCM that is not GitLab-class)
4245

43-
For runtime features (`GITHUB_COPILOT_PAT`), see [Agent Workflows](../../guides/agent-workflows/).
46+
| Priority | Source | Notes |
47+
|----------|--------|-------|
48+
| 1 | `git credential fill` | Configure credentials for that host in git |
49+
50+
For Azure DevOps, APM resolves `ADO_APM_PAT`, then an Entra ID (AAD) bearer token from Azure CLI (`az`). See [Azure DevOps](#azure-devops).
51+
52+
For Artifactory registry proxies, use `PROXY_REGISTRY_TOKEN`. See [Registry proxy (Artifactory)](#registry-proxy-artifactory).
53+
54+
For Copilot/runtime token variables (`GITHUB_COPILOT_PAT`, etc.), see [Agent Workflows](../../guides/agent-workflows/).
4455

4556
### Configuration variables
4657

@@ -66,6 +77,33 @@ The org name comes from the dependency reference — `contoso/my-package` checks
6677

6778
Per-org tokens take priority over global tokens. Use this when different orgs require different PATs (e.g., separate SSO authorizations).
6879

80+
## Multi-account Git Credential Manager
81+
82+
APM forwards the repository path to `git credential fill`, so [Git Credential Manager (GCM)](https://github.com/git-ecosystem/git-credential-manager) can automatically pick the right GitHub account per organization -- no account-picker prompt. Existing single-account setups are unaffected: if `credential.useHttpPath` is not enabled, git credential helpers ignore the `path` attribute and match per host only.
83+
84+
To opt in, enable path-aware matching once:
85+
86+
```bash
87+
git config --global credential.useHttpPath true
88+
```
89+
90+
GCM (v2.1+) matches credential URLs by **prefix**, so a single config entry per org typically covers every repo under that org:
91+
92+
```bash
93+
git config --global credential.https://github.com/acme.username your-acme-account
94+
git config --global credential.https://github.com/personal-org.username your-personal-account
95+
```
96+
97+
With the entries above, fetches against `acme/widgets`, `acme/payments`, and any other `acme/*` repo all resolve to `your-acme-account` without per-repo configuration. Other credential helpers (and older GCM versions) may require an exact path match -- consult your helper's documentation if a per-org entry is not picked up.
98+
99+
### Seeing an account picker mid-install?
100+
101+
If `apm install` triggers a GCM account-picker dialog while resolving a private repo:
102+
103+
1. Confirm `credential.useHttpPath` is set globally: `git config --global --get credential.useHttpPath` should print `true`.
104+
2. Confirm a per-URL entry exists for the org: `git config --global --get-urlmatch credential https://github.com/<org>` should list the username.
105+
3. Re-run with `--verbose`; APM logs `trying git credential fill for <host> (path=<owner>/<repo>)` so you can confirm the path APM is sending matches your config entry.
106+
69107
## Fine-grained PAT setup
70108

71109
Fine-grained PATs (`github_pat_`) are scoped to a **single resource owner** — either a user account or an organization. A user-scoped fine-grained PAT **cannot** access repos owned by an organization, even if you are a member of that org.
@@ -213,14 +251,32 @@ Bearer tokens are short-lived (~60 minutes), acquired on demand, never persisted
213251
214252
When authentication fails, APM prints a targeted diagnostic instead of a generic "not accessible or doesn't exist" message. The diagnostic tells you exactly which path failed and what to do next. For `--update` operations, APM verifies auth *before* modifying any files -- if the pre-flight check fails, you will see `No files were modified` and your `apm.yml`, `apm.lock.yaml`, and `apm_modules/` directory remain untouched.
215253
254+
## GitLab (SaaS and self-managed)
255+
256+
### Host classification
257+
258+
APM must classify a host as GitLab to use **GitLab REST v4** (for example `marketplace.json` fetches and install-time single-file reads). Configuration mirrors GHES-style host overrides:
259+
260+
| Variable | Purpose |
261+
|----------|---------|
262+
| `GITLAB_HOST` | One self-managed GitLab FQDN (e.g. `git.company.com`) |
263+
| `APM_GITLAB_HOSTS` | Several self-managed GitLab FQDNs, comma-separated |
264+
265+
`gitlab.com` is detected automatically. For GitLab-class hosts, resolved credentials follow **`GITLAB_APM_PAT` → `GITLAB_TOKEN`** and then **`git credential fill`** (see [GitLab-class hosts](#gitlab-class-hosts-gitlabcom-gitlab_host-apm_gitlab_hosts) under [Token lookup](#token-lookup)). GitHub PAT env vars are not used on GitLab. Use a GitLab personal or project access token with API read access where your policy requires it.
266+
267+
### REST headers (GitLab vs GitHub)
268+
269+
For GitHub and GHES, APM sends repository API requests with `Authorization: token <PAT>` (or equivalent). For **GitLab REST v4**, PATs are sent with the **`PRIVATE-TOKEN`** header (GitLab’s convention). OAuth-style access tokens can use `Authorization: Bearer` when applicable. APM does not log token values.
270+
216271
## Package source behavior
217272
218273
| Package source | Host | Auth behavior | Fallback |
219274
|---|---|---|---|
220-
| `org/repo` (bare) | `default_host()` | Global env vars → `gh auth token` → credential fill | Unauth for public repos |
221-
| `github.com/org/repo` | github.com | Global env vars → `gh auth token` → credential fill | Unauth for public repos |
222-
| `contoso.ghe.com/org/repo` | *.ghe.com | Global env vars → `gh auth token` → credential fill | Auth-only (no public repos) |
223-
| GHES via `GITHUB_HOST` | ghes.company.com | Global env vars → `gh auth token` → credential fill | Unauth for public repos |
275+
| `org/repo` (bare) | `default_host()` | Global env vars -> `gh auth token` -> credential fill | Unauth for public repos |
276+
| `github.com/org/repo` | github.com | Global env vars -> `gh auth token` -> credential fill | Unauth for public repos |
277+
| `contoso.ghe.com/org/repo` | *.ghe.com | Global env vars -> `gh auth token` -> credential fill | Auth-only (no public repos) |
278+
| GHES via `GITHUB_HOST` | ghes.company.com | Global env vars -> `gh auth token` -> credential fill | Unauth for public repos |
279+
| GitLab (`gitlab.com` or host listed in `GITLAB_HOST` / `APM_GITLAB_HOSTS`) | gitlab.com or self-managed | `GITLAB_APM_PAT` -> `GITLAB_TOKEN` -> credential helper; REST uses `PRIVATE-TOKEN`; GitHub env vars excluded | Unauth where the instance allows it |
224280
| `dev.azure.com/org/proj/repo` | ADO | `ADO_APM_PAT` -> AAD bearer via `az` | Auth-only |
225281
| Artifactory registry proxy | custom FQDN | `PROXY_REGISTRY_TOKEN` | Error if `PROXY_REGISTRY_ONLY=1` |
226282
@@ -306,27 +362,35 @@ Run with `--verbose` to see the full resolution chain:
306362
apm install --verbose your-org/package
307363
```
308364

309-
The output shows which env var matched (or `none`), the detected token type (`fine-grained`, `classic`, `oauth`, `github-app`), and the host classification (`github`, `ghe_cloud`, `ghes`, `ado`, `generic`).
365+
The output shows which env var matched (or `none`), the detected token type (`fine-grained`, `classic`, `oauth`, `github-app`), and the host classification (`github`, `ghe_cloud`, `ghes`, `ado`, `gitlab`, `generic`).
310366

311-
The full resolution and fallback flow:
367+
The full resolution and fallback flow (simplified):
312368

313369
```mermaid
314370
flowchart TD
315-
A[Dependency Reference] --> B{Per-org env var?}
371+
A[Dependency Reference] --> HC{Host class?}
372+
373+
HC -->|GitHub / GHE Cloud / GHES| B{Per-org env var?}
316374
B -->|GITHUB_APM_PAT_ORG| C[Use per-org token]
317375
B -->|Not set| D{Global env var?}
318376
D -->|GITHUB_APM_PAT / GITHUB_TOKEN / GH_TOKEN| E[Use global token]
319-
D -->|Not set| F{gh auth token?<br/>GitHub-like hosts only}
320-
F -->|Found| G[Use gh token]
321-
F -->|Not found| H{Git credential fill?}
322-
H -->|Found| J[Use credential]
323-
H -->|Not found| K[No token]
377+
D -->|Not set| GH{gh auth token?<br/>GitHub-like hosts only}
378+
GH -->|Found| E
379+
GH -->|Not found| F{Git credential fill?}
380+
381+
HC -->|GitLab| GL{GitLab env var?}
382+
GL -->|GITLAB_APM_PAT / GITLAB_TOKEN| E
383+
GL -->|Not set| F
384+
385+
HC -->|Generic FQDN| F
386+
387+
F -->|Found| G[Use credential]
388+
F -->|Not found| H[No token]
324389
325390
E --> I{try_with_fallback}
326391
C --> I
327392
G --> I
328-
J --> I
329-
K --> I
393+
H --> I
330394
331395
I -->|Token works| L[Success]
332396
I -->|Token fails| M{Fallback credentials}

0 commit comments

Comments
 (0)