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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- The anonymous-first `github.com` HTTPS attempt now inherits `http.sslBackend`/`http.sslCAInfo` from the real (non-isolated) git config (including the credentialed retry and `--auth-first` bypass), and `--auth-first` / `APM_GITHUB_AUTH_FIRST` / `apm config set github-auth-first true` skip that attempt entirely as an escape hatch for networks behind a TLS-inspecting proxy whose intercepting CA chain has no revocation info at all. (#2545)

## [0.31.0] - 2026-09-15

### Added
Expand Down
27 changes: 25 additions & 2 deletions docs/src/content/docs/getting-started/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ Installing or updating the CLI queries public release metadata authenticated-fir

Public `github.com` packages need no token configuration. APM tries HTTPS repository operations anonymously before resolving credentials.

The first attempt has no authorization header, GitHub token environment variable, or active Git credential helper. APM preserves caller-supplied Git config, including URL rewrites, CA settings, and non-credential HTTP headers, while removing credential-bearing headers and helpers. A 401, 403, 404, or equivalent Git authentication failure unlocks the credential chain below; DNS, TLS, timeout, and GitHub throttle failures do not.
The first attempt has no authorization header, GitHub token environment variable, or active Git credential helper. APM preserves caller-supplied Git config, including URL rewrites, CA settings, and non-credential HTTP headers, while removing credential-bearing headers and helpers. It also inherits `http.sslBackend`/`http.sslCAInfo` from your real (non-isolated) Git config, so a corporate TLS trust store or `sslBackend` setting still applies to the anonymous attempt. A 401, 403, 404, or equivalent Git authentication failure unlocks the credential chain below; DNS, TLS, timeout, and GitHub throttle failures do not.

If your network sits behind a TLS-inspecting proxy whose intercepting CA chain carries no revocation info at all, the anonymous attempt's isolated environment can still fail a TLS handshake even with that inheritance. `--auth-first` / `APM_GITHUB_AUTH_FIRST=1` / `apm config set github-auth-first true` skip the anonymous attempt entirely for exact-host `github.com` and go straight to your resolved credentials/environment. See [TLS-inspecting proxy blocks the anonymous attempt](#tls-inspecting-proxy-blocks-the-anonymous-attempt).
Comment thread
rgarciapariente marked this conversation as resolved.

APM resolves ordinary tokens per `(host, port, org)` scope. When a private `github.com` fallback asks the credential helper for a repository path, that path also scopes the cache entry. APM then walks a **host-class-specific** chain until it finds a token:

Expand Down Expand Up @@ -130,6 +132,7 @@ For Copilot/runtime token variables (`GITHUB_COPILOT_PAT`, etc.), see [Authentic
| `GITHUB_HOST` | Default host for bare package names (e.g., GHES hostname) |
| `ADO_HOST` | One on-prem Azure DevOps Server FQDN, without a port or path (e.g., `ado.company.com`) |
| `APM_ADO_HOSTS` | Comma-separated list of on-prem ADO Server FQDNs, without ports or paths |
| `APM_GITHUB_AUTH_FIRST` | `1`/`true` skips the anonymous-first HTTPS attempt for exact-host `github.com` (also: `--auth-first`, `apm config set github-auth-first true`). See [TLS-inspecting proxy blocks the anonymous attempt](#tls-inspecting-proxy-blocks-the-anonymous-attempt) |

## Multi-org setup

Expand Down Expand Up @@ -513,6 +516,24 @@ APM tries unauthenticated access first for public repos to conserve rate limits
export GITHUB_TOKEN=ghp_any_valid_token
```

### TLS-inspecting proxy blocks the anonymous attempt

APM's anonymous-first attempt for `github.com` inherits `http.sslBackend`/`http.sslCAInfo` from your real (non-isolated) Git config, so a corporate TLS trust store usually works out of the box. If your network sits behind a TLS-inspecting proxy (e.g. Netskope) whose intercepting CA chain has **no CRL Distribution Point and no Authority Information Access (OCSP) extension at all** -- not just unreachable, genuinely absent -- the isolated attempt may still fail the TLS handshake even with that inheritance, while a plain `git clone` on the same machine succeeds because it also uses your credential helper.

Symptoms: `apm install` fails with a network/TLS-classified error (e.g. `CRYPT_E_NO_REVOCATION_CHECK` on Windows, or a hang) rather than an authentication error, and `--allow-protocol-fallback` jumps straight to SSH, which may also be blocked.

Skip the anonymous attempt and use your resolved credentials/environment from the first HTTPS attempt:

```bash
apm install --auth-first
# or
export APM_GITHUB_AUTH_FIRST=1
# or, to always do this on this machine
apm config set github-auth-first true
```

This trades the anonymous-first credential-isolation property for environment-inheriting behavior; APM prints a one-time `[!]` warning when it is active. `--auth-first` only affects exact-host `github.com`; GHE Cloud, GHES, ADO, GitLab, and generic hosts already resolve credentials first.

### SSO-protected organizations

Authorize your PAT for SSO at [github.com/settings/tokens](https://github.com/settings/tokens) — click **Configure SSO** next to the token.
Expand Down Expand Up @@ -541,10 +562,12 @@ The full resolution and fallback flow (simplified):
flowchart TD
A[Dependency Reference] --> HC{Host class?}

HC -->|github.com HTTPS| ANON[Anonymous attempt<br/>tokens and helpers fenced]
HC -->|github.com HTTPS| AF{auth-first opt-out?}
AF -->|"No: default"| ANON["Anonymous attempt<br/>tokens and helpers fenced<br/>real http.sslBackend/sslCAInfo inherited"]
ANON -->|Success| L[Success]
ANON -->|401 / 403 / 404<br/>or Git auth failure| B{Per-org env var?}
ANON -->|DNS / TLS / timeout<br/>or rate limit| Q[Surface non-auth failure]
AF -->|"Yes: --auth-first / APM_GITHUB_AUTH_FIRST"| B

HC -->|GHE Cloud / GHES| B
B -->|GITHUB_APM_PAT_ORG| C[Use per-org token]
Expand Down
28 changes: 23 additions & 5 deletions docs/src/content/docs/reference/cli/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Write `KEY` to `~/.apm/config.json`. Validates the value before writing:

### `apm config unset KEY`

Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Supported unset keys: `target`, `self-update.channel`, `self-update.install-dir`, `temp-dir`, `copilot-cowork-skills-dir`, `prefer-ssh`, `allow-protocol-fallback`, `audit-on-install`, `external.<name>.{llm,args}`, `mcp-registry-url`, and `registry.<name>.{url,token,default}`. After unsetting a key the effective value falls back to the environment variable, then the built-in default. Other boolean keys are reset by `set`-ing them to their default.
Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Supported unset keys: `target`, `self-update.channel`, `self-update.install-dir`, `temp-dir`, `copilot-cowork-skills-dir`, `prefer-ssh`, `allow-protocol-fallback`, `github-auth-first`, `audit-on-install`, `external.<name>.{llm,args}`, `mcp-registry-url`, and `registry.<name>.{url,token,default}`. After unsetting a key the effective value falls back to the environment variable, then the built-in default. Other boolean keys are reset by `set`-ing them to their default.

## Configuration keys

Expand All @@ -71,6 +71,7 @@ Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Supported u
| `temp-dir` | path | system temp | Directory used for clone and download operations. Useful when the OS temp directory is locked down (for example, corporate Windows endpoints rejecting `%TEMP%` with `[WinError 5]`). |
| `allow-protocol-fallback` | boolean | `false` | Enable the legacy cross-protocol fallback chain. When true, APM retries a failed clone with the opposite protocol (SSH -> HTTPS or HTTPS -> SSH). Equivalent to `--allow-protocol-fallback` or `APM_ALLOW_PROTOCOL_FALLBACK=1`. |
| `prefer-ssh` | boolean | `false` | Prefer SSH transport for shorthand (`owner/repo`) dependencies. Equivalent to `--ssh` or `APM_GIT_PROTOCOL=ssh`. |
| `github-auth-first` | boolean | `false` | Skip the anonymous-first HTTPS attempt for exact-host `github.com` and resolve credentials/environment immediately. Escape hatch for TLS-inspecting proxies whose intercepting CA chain has no revocation info. Equivalent to `--auth-first` or `APM_GITHUB_AUTH_FIRST=1`. |
| `copilot-cowork-skills-dir` | absolute path | auto-detected | Override the resolved Cowork OneDrive skills directory. Requires the `copilot-cowork` experimental flag for `set`. |
| `audit-on-install` | enum | `off` | Default content-audit mode for `apm install`: `off` / `warn` / `block`. `warn` records findings in the install summary; `block` halts on critical findings. Overridable per-install with `--audit` / `--no-audit`; an org policy `security.audit.on_install` floor can raise it. Requires the `external-scanners` experimental flag for `set`. |
| `external.<name>.llm` | boolean | unset | Opt a SARIF scanner into LLM-powered analysis (`<name>` validated against supported scanners). SkillSpector default is offline. LLM mode makes outbound API calls and needs `OPENAI_API_KEY` or `NVIDIA_INFERENCE_KEY`. Overridable per-run with `--external-llm` / `--no-external-llm`. Requires the `external-scanners` experimental flag. |
Expand Down Expand Up @@ -100,13 +101,21 @@ only. Plaintext `http://` needs `MCP_REGISTRY_ALLOW_HTTP=1` when it arrives from
the environment or from `apm.yml`; a URL you persisted with `apm config set`
needs no opt-in, since setting it is already the explicit choice.

`allow-protocol-fallback` and `prefer-ssh` follow the layered transport precedence:
`allow-protocol-fallback` and `prefer-ssh` (transport selection) follow the layered precedence:

1. CLI flag (`--allow-protocol-fallback`, `--ssh`) -- highest priority
2. Environment variable (`APM_ALLOW_PROTOCOL_FALLBACK=1`, `APM_GIT_PROTOCOL=ssh`)
3. Value in `~/.apm/config.json` (`apm config set ...`)
4. Built-in default (`false` / no preference)

`github-auth-first` follows the same layered shape, but it is a credential-order
setting, not a transport selector -- see [`apm install`'s `--auth-first`](../install/#cache-and-network):

1. CLI flag (`--auth-first`) -- highest priority
2. Environment variable (`APM_GITHUB_AUTH_FIRST=1`)
3. Value in `~/.apm/config.json` (`apm config set github-auth-first true`)
4. Built-in default (`false`: anonymous-first for exact-host `github.com`)

Registry tokens are resolved as:

1. `APM_REGISTRY_TOKEN_<NAME>` environment variable (uppercase name, `-`/`.` -> `_`)
Expand Down Expand Up @@ -185,6 +194,15 @@ apm config set allow-protocol-fallback true
apm config get allow-protocol-fallback
```

Persist the github.com auth-first opt-out (for networks behind a TLS-inspecting proxy whose intercepting CA chain has no revocation info):

```bash
apm config set github-auth-first true
apm config get github-auth-first
# Remove the persisted preference:
apm config unset github-auth-first
```

Pin a writable temp directory on Windows:

```bash
Expand Down Expand Up @@ -248,13 +266,13 @@ See [External scanners](../../../integrations/external-scanners/).
- **Format:** JSON object, one entry per stored key.
- **Created on first read** with `{"default_client": "vscode"}`. Hand-editing is supported but `apm config set` is preferred -- it validates input and normalizes paths.

Internal JSON keys use snake_case (`auto_integrate`, `install_target`, `self_update_channel`, `self_update_install_dir`, `temp_dir`, `allow_protocol_fallback`, `prefer_ssh`, `copilot_cowork_skills_dir`); CLI keys use kebab-case or dotted namespaces (the CLI `target` key is stored as `install_target`). The CLI translates between the two.
Internal JSON keys use snake_case (`auto_integrate`, `install_target`, `self_update_channel`, `self_update_install_dir`, `temp_dir`, `allow_protocol_fallback`, `prefer_ssh`, `github_auth_first`, `copilot_cowork_skills_dir`); CLI keys use kebab-case or dotted namespaces (the CLI `target` key is stored as `install_target`). The CLI translates between the two.

## Related

- [`apm install`](../install/) -- consumes `target`, `temp-dir`, and `allow-protocol-fallback` / `prefer-ssh`.
- [`apm install`](../install/) -- consumes `target`, `temp-dir`, and `allow-protocol-fallback` / `prefer-ssh` / `github-auth-first`.
- [`apm self-update`](../self-update/) -- consumes `self-update.channel` and `self-update.install-dir`.
- [`apm compile`](../compile/) -- affected by `auto-integrate`.
- [`apm experimental`](../experimental/) -- gates `copilot-cowork-skills-dir` and `registry.*` keys.
- [Environment variables](../../environment-variables/) -- `APM_ALLOW_PROTOCOL_FALLBACK`, `APM_GIT_PROTOCOL` are the env-var equivalents of the transport keys.
- [Environment variables](../../environment-variables/) -- `APM_ALLOW_PROTOCOL_FALLBACK` and `APM_GIT_PROTOCOL` are the env-var equivalents of the transport keys; `APM_GITHUB_AUTH_FIRST` is the env-var equivalent of the credential-order `github-auth-first` key.
- [Registries](../../../guides/registries/) -- full private registry setup guide.
3 changes: 2 additions & 1 deletion docs/src/content/docs/reference/cli/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ you use the generated registration.
| `--ssh` | off | Prefer SSH transport for shorthand (`owner/repo`) deps. Mutually exclusive with `--https`. |
| `--https` | off | Prefer HTTPS transport for shorthand deps. Mutually exclusive with `--ssh`. |
| `--allow-protocol-fallback` | off | Restore the legacy permissive HTTPS<->SSH fallback chain. Env: `APM_ALLOW_PROTOCOL_FALLBACK=1`. |
| `--auth-first` | off | Skip the anonymous-first HTTPS attempt for exact-host `github.com` and resolve credentials/environment immediately. Escape hatch for TLS-inspecting proxies whose intercepting CA chain has no revocation info. Env: `APM_GITHUB_AUTH_FIRST=1`. |

Transport env vars: `APM_GIT_PROTOCOL` (`ssh` or `https`) sets the default initial transport for shorthand deps; `APM_ALLOW_PROTOCOL_FALLBACK=1` mirrors `--allow-protocol-fallback`.
Transport env vars: `APM_GIT_PROTOCOL` (`ssh` or `https`) sets the default initial transport for shorthand deps; `APM_ALLOW_PROTOCOL_FALLBACK=1` mirrors `--allow-protocol-fallback`. Credential-order env var: `APM_GITHUB_AUTH_FIRST=1` mirrors `--auth-first` (not a transport selector -- see [Authentication: TLS-inspecting proxy blocks the anonymous attempt](../../getting-started/authentication/#tls-inspecting-proxy-blocks-the-anonymous-attempt)).

### Skill subset

Expand Down
5 changes: 3 additions & 2 deletions docs/src/content/docs/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ PAT / bearer credentials APM reads when cloning packages, calling host APIs, or
| `GIT_SSH_COMMAND` | Standard git SSH command override. APM reads it before composing its own SSH env. | unset | If you set it, APM preserves your value. |
| `APM_GIT_CREDENTIAL_TIMEOUT` | Seconds to wait for a `git credential fill` response. | implementation default | Integer-like string; invalid values are ignored. |

## Transport and protocol
## Transport and credential order

Controls how APM clones packages and enumerates refs on Git hosts. These settings can also be persisted via [`apm config set`](../cli/config/) to avoid repeating flags or environment-variable exports.
Controls how APM clones packages and enumerates refs on Git hosts: `APM_GIT_PROTOCOL` and `APM_ALLOW_PROTOCOL_FALLBACK` select the transport scheme, while `APM_GITHUB_AUTH_FIRST` changes credential order for exact-host `github.com` without affecting transport. These settings can also be persisted via [`apm config set`](../cli/config/) to avoid repeating flags or environment-variable exports.

| Variable | Purpose | Default | Notes |
|---|---|---|---|
| `APM_GIT_PROTOCOL` | Preferred Git protocol for cloning and ref enumeration on shorthand (`owner/repo`) dependencies. Accepted values: `ssh`, `https`. | unset | Equivalent to `--ssh` / `--https` flag. Resolution: CLI flag → env var → `prefer-ssh` key in `~/.apm/config.json` → git `insteadOf` rules → HTTPS. |
| `APM_ALLOW_PROTOCOL_FALLBACK` | Set to `1` (or `true`/`yes`/`on`) to enable the legacy cross-protocol fallback chain. When enabled, a failed clone is retried with the opposite protocol. | unset | Equivalent to `--allow-protocol-fallback`. Resolution: CLI flag → env var → `allow-protocol-fallback` key in `~/.apm/config.json` → `false`. |
| `APM_GITHUB_AUTH_FIRST` | Set to `1` (or `true`/`yes`/`on`) to skip the anonymous-first HTTPS attempt for exact-host `github.com` and resolve credentials/environment immediately. Escape hatch for TLS-inspecting proxies whose intercepting CA chain has no revocation info. | unset | Equivalent to `--auth-first`. Resolution: CLI flag → env var → `github-auth-first` key in `~/.apm/config.json` → `false`. See [Authentication: TLS-inspecting proxy blocks the anonymous attempt](../getting-started/authentication/#tls-inspecting-proxy-blocks-the-anonymous-attempt). |

## TLS trust

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ CLI bootstrap/update metadata recovery is separate from package authentication.

## Token precedence chain

For public `github.com` HTTPS repositories, APM makes one anonymous attempt before checking any token source. The attempt removes GitHub token variables, credential-bearing HTTP headers, and credential helpers while preserving CA settings, safe URL rewrites, non-credential HTTP headers, and `credential.interactive=never`.
For public `github.com` HTTPS repositories, APM makes one anonymous attempt before checking any token source. The attempt removes GitHub token variables, credential-bearing HTTP headers, and credential helpers while preserving CA settings, safe URL rewrites, non-credential HTTP headers, and `credential.interactive=never`. It also inherits `http.sslBackend`/`http.sslCAInfo` from the real (non-isolated) git config, so a corporate TLS trust store or `sslBackend` setting still applies.

If a network sits behind a TLS-inspecting proxy whose intercepting CA chain carries no revocation info at all, the isolated attempt can still fail the TLS handshake even with that inheritance. `--auth-first` / `APM_GITHUB_AUTH_FIRST=1` / `apm config set github-auth-first true` skip the anonymous attempt entirely for exact-host `github.com` and resolve credentials/environment immediately, emitting a one-time `[!]` warning when active. See [Authentication: TLS-inspecting proxy blocks the anonymous attempt](https://microsoft.github.io/apm/getting-started/authentication/#tls-inspecting-proxy-blocks-the-anonymous-attempt).

Only HTTP 401, 403, 404, or an equivalent Git authentication failure unlocks the fallback chain below. DNS, TLS, timeout, and GitHub throttle failures do not prompt for credentials. Ordinary credentials are cached per `(host, port, org)` for the process. A private `github.com` helper fallback adds the repository path to that scope, so later phases reuse it without applying that credential to another repository. APM never writes the credential into persistent cache keys or stored remote URLs.

Expand Down
Loading
Loading