Skip to content

Add additive corporate-CA support: APM_EXTRA_CA_BUNDLE (npm NODE_EXTRA_CA_CERTS parity) #2034

Description

Context

Fast-follow from the #2005 / #2004 OS-trust-store work. A red-team review (enterprise-proxy + package-manager-precedent panels) flagged that APM has no additive CA path: REQUESTS_CA_BUNDLE / CURL_CA_BUNDLE replace the trust set (and skip truststore injection entirely). Many enterprises hand developers a single corporate-root PEM but do not permit OS trust-store edits, so they need "OS roots plus this one extra CA", not "only this CA".

This is the standard npm model (NODE_EXTRA_CA_CERTS, additive) and Node's --use-system-ca (system + bundled + extra). APM lacks the equivalent.

Proposal

Add an additive APM_EXTRA_CA_BUNDLE (and optionally APM_EXTRA_CA_DIR) that is layered on top of the OS trust store rather than replacing it:

  • Implement via a scoped SSLContext that first loads the OS/certifi defaults, then load_verify_locations(APM_EXTRA_CA_BUNDLE) — OS/certifi defaults remain intact.
  • Propagate to apm run child runtimes through the same build_child_tls_env() seam introduced in feat(tls): verify against the OS trust store by default (closes #2004) #2005 (export the extra bundle so Python and Node children both honor it, e.g. NODE_EXTRA_CA_CERTS for Node runtimes).
  • Precedence must be documented explicitly alongside the existing REQUESTS_CA_BUNDLE (replace) and APM_DISABLE_TRUSTSTORE (opt-out) knobs.

Acceptance

  • An enterprise PEM set via APM_EXTRA_CA_BUNDLE is trusted in addition to the OS store for both apm install and apm run.
  • e2e test: private-CA server trusted via APM_EXTRA_CA_BUNDLE while public HTTPS (OS/certifi roots) still verifies.

Deferred deliberately from #2005 to avoid half-shipping (Node-only) the additive path.

Activity

  1. github-actions commented on Jul 5, 2026

    @github-actions

    Triage decision

    accept

    Proposed labels

    theme/security
    area/enterprise
    area/docs-site
    type/feature
    status/accepted
    priority/high
    

    Milestone

    null -- No open milestone matching the v0.24.x release series was found. This is a priority/high enterprise feature; maintainer should assign to the appropriate next-minor milestone.

    Suggested next action

    Draft the implementation plan for the SSLContext builder and child-runtime propagation strategy (especially how Python child processes via requests will honor the extra CA without the REQUESTS_CA_BUNDLE replacement footgun) before opening a PR.

    Suggested issue comment

    This is accepted -- the REQUESTS_CA_BUNDLE footgun (replaces the trust set, breaks public HTTPS for
    corporate CA users) is a real enterprise blocker, and NODE_EXTRA_CA_CERTS parity is the right model.
    The additive APM_EXTRA_CA_BUNDLE approach is the correct design.
    
    A few implementation notes worth settling before the PR opens:
    
    1. **Parent process**: SSLContext with load_verify_locations(APM_EXTRA_CA_BUNDLE) on top of OS/certifi
       defaults is the right Python primitive.
    2. **Node children via apm run**: NODE_EXTRA_CA_CERTS export via build_child_tls_env() is
       straightforward.
    3. **Python children via apm run**: `requests` does not use SSLContext directly -- the cleanest
       propagation is writing a merged PEM bundle to a temp file and setting REQUESTS_CA_BUNDLE to it,
       which avoids the replacement footgun for children too. This detail is worth capturing in the PR
       description.
    
    The acceptance criteria (e2e test: private-CA server trusted via APM_EXTRA_CA_BUNDLE while public
    HTTPS on OS/certifi roots still verifies) are concrete and testable. Documenting the precedence
    (APM_EXTRA_CA_BUNDLE additive / REQUESTS_CA_BUNDLE replace / APM_DISABLE_TRUSTSTORE opt-out) in
    enterprise/security.md is a required part of the implementing PR.

    Per-lens notes (collapsed)

    DevX UX Expert -- User-Need Reviewer

    The current state -- REQUESTS_CA_BUNDLE replaces the trust set -- is a genuine enterprise UX failure:
    developers must choose between trusting their corporate CA and trusting the public internet. APM_EXTRA_CA_BUNDLE
    resolves this by layering on top of OS/certifi defaults, matching the NODE_EXTRA_CA_CERTS mental model
    that Node.js ecosystem developers already know. The naming is clear, the behavior is predictable, and
    the acceptance criteria (e2e test) are concrete. Documenting the precedence table (additive vs replace
    vs opt-out) is essential -- enterprise developers need to reason about the behavior without trial and
    error.

    Supply Chain Security Expert -- Risk-Surface Reviewer

    This touches the TLS trust surface. Key security notes:
    (1) The additive approach is correct and safe -- load_verify_locations() extends, not replaces, the
    trust set. No weakening of existing verification.
    (2) APM_EXTRA_CA_BUNDLE path must be validated at startup: file must exist and be a readable PEM
    bundle; no path traversal should be possible.
    (3) Child runtime propagation requires care: exporting NODE_EXTRA_CA_CERTS for Node children is safe
    (additive by design). For Python children using requests, propagating a merged PEM bundle via
    REQUESTS_CA_BUNDLE avoids the replacement footgun but introduces a temp-file lifecycle question.
    (4) Precedence documentation is a security-auditor requirement: enterprises need to understand which
    knob wins when multiple CA env vars are set.
    theme/security applies (TLS trust configuration). area/enterprise is the primary area.

    OSS Growth Hacker -- Contributor-Tone Reviewer

    Not activated -- danielmeppiel is the site admin and project maintainer; no first-time contributor tone
    adjustment is needed for this issue.

    Python Architect -- Architecture Reviewer

    Implementation requires: (1) A new TLS utility (likely in utils/tls.py or extending existing TLS
    helpers) that builds an SSLContext loading OS/certifi defaults + APM_EXTRA_CA_BUNDLE. (2) All HTTP
    requests.Session creation points must use the custom SSLContext (via requests.adapters.HTTPAdapter
    with a custom ssl_context). (3) build_child_tls_env() (introduced in #2005) extended to export
    NODE_EXTRA_CA_CERTS for Node children. (4) For Python children that use requests, the cleanest
    propagation is writing a merged PEM bundle to a temp file and setting REQUESTS_CA_BUNDLE -- this
    avoids the replacement footgun but adds a temp-file lifecycle concern. The issue is correctly scoped
    and the architectural seam (#2005) exists. No status/needs-design needed for the happy path;
    the Python-child propagation detail is worth capturing in the PR description before coding.

    Doc Writer -- Documentation Reviewer

    The implementing PR needs to update docs/src/content/docs/enterprise/security.md to include:
    (1) APM_EXTRA_CA_BUNDLE in the environment variable reference alongside REQUESTS_CA_BUNDLE and
    APM_DISABLE_TRUSTSTORE, with a clear precedence table (additive vs replace vs opt-out).
    (2) A note on how APM_EXTRA_CA_BUNDLE propagates to child runtimes via apm run (Node children:
    NODE_EXTRA_CA_CERTS; Python children: merged bundle). area/docs-site is warranted as a label since
    the security model documentation requires a substantive addition. The suggested comment wording
    correctly flags the Python-child propagation detail as something to capture before the PR opens.

    APM CEO -- Triage Arbiter

    Well-scoped enterprise feature from the maintainer, filling a real gap: REQUESTS_CA_BUNDLE-as-replace
    is a known footgun that blocks enterprise adoption for corporate CA customers. NODE_EXTRA_CA_CERTS
    parity is a competitive-positioning win (APM matches Node.js behavior on a table-stakes enterprise
    requirement). The theme/security label is correct (TLS trust configuration). area/enterprise is
    the primary area; area/docs-site rides because the security model documentation requires a
    substantive update. priority/high reflects the enterprise-blocker status. The Python-child
    propagation detail (how to avoid the REQUESTS_CA_BUNDLE replacement footgun for children) is worth
    settling in an implementation note before the PR opens -- but it is not a design blocker for accepting
    the issue. Decision: accept. Milestone: null -- no open milestone for v0.24.x; maintainer assigns.

    {
      "decision": "accept",
      "decision_detail": "",
      "theme": "theme/security",
      "areas": ["area/enterprise", "area/docs-site"],
      "type": "type/feature",
      "status": "status/accepted",
      "priority": "priority/high",
      "preserved_labels": [],
      "milestone": null,
      "next_action": "Draft the implementation plan for the SSLContext builder and Python-child propagation strategy (merged PEM temp bundle vs REQUESTS_CA_BUNDLE) before opening a PR.",
      "comment_markdown": "This is accepted -- the REQUESTS_CA_BUNDLE footgun (replaces the trust set, breaks public HTTPS for corporate CA users) is a real enterprise blocker, and NODE_EXTRA_CA_CERTS parity is the right model. The additive APM_EXTRA_CA_BUNDLE approach is the correct design.\n\nA few implementation notes worth settling before the PR opens:\n\n1. Parent process: SSLContext with load_verify_locations(APM_EXTRA_CA_BUNDLE) on top of OS/certifi defaults is the right Python primitive.\n2. Node children via apm run: NODE_EXTRA_CA_CERTS export via build_child_tls_env() is straightforward.\n3. Python children via apm run: requests does not use SSLContext directly -- the cleanest propagation is writing a merged PEM bundle to a temp file and setting REQUESTS_CA_BUNDLE to it, which avoids the replacement footgun for children too.\n\nThe acceptance criteria (e2e test: private-CA server trusted via APM_EXTRA_CA_BUNDLE while public HTTPS on OS/certifi roots still verifies) are concrete and testable. Documenting the precedence (APM_EXTRA_CA_BUNDLE additive / REQUESTS_CA_BUNDLE replace / APM_DISABLE_TRUSTSTORE opt-out) in enterprise/security.md is a required part of the implementing PR."
    }

    Triage status: agentic proposal pending human ratification.
    Silence is approval. Maintainers can:

    • Override any label or milestone above by editing it directly --
      human edits are authoritative and will not be reverted on
      subsequent runs.
    • Re-trigger triage by applying the status/needs-triage label, or
      by removing status/triaged to enroll the issue in the next
      daily sweep.

    Posted by the Triage Panel workflow. See .apm/skills/apm-triage-panel for the panel skill.

    Generated by Triage Panel · 129.2 AIC · ⌖ 22.4 AIC · ⊞ 9.2K · ◷

  2. added
    area/docs-sitedocs/src/content (Starlight), README, doc generation.
    area/enterpriseAir-gapped/GHE configurability, registry proxy, rulesets, adoption playbook.
    priority/highHuman-set high priority; not scope approval, a release commitment or a required milestone.
    status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.
    status/triagedAutomated advice completed; deduplication only. Not human approval; silence is not approval.
    theme/securitySecure by default. Content scanning, lockfile integrity, MCP trust boundaries.
    type/featureNew capability, new flag, new primitive.
    on Jul 5, 2026
  3. TameTheGame commented on Aug 29, 2026

    @TameTheGame

    Implementation plan before opening the PR:

    1. Keep src/apm_cli/core/tls_trust.py as the single trust-policy owner. Validate a non-empty APM_EXTRA_CA_BUNDLE as a readable, regular, parseable PEM before any HTTP module is imported. Invalid input will fail closed with an actionable error; verification is never disabled.
    2. Preserve explicit precedence: APM_DISABLE_TRUSTSTORE > REQUESTS_CA_BUNDLE / CURL_CA_BUNDLE (replacement) > APM_EXTRA_CA_BUNDLE (additive) > OS trust store > certifi fallback.
    3. For the parent process, inject truststore as today, then install a scoped truststore.SSLContext subclass whose constructor loads the validated extra PEM. Wire that context at the existing process-wide SSL/urllib3 seam and feature-detect Requests' optional preloaded context. This covers direct Requests calls, existing Sessions, custom adapters, and proxy pools without creating a second HTTP-session authority.
    4. For Python child runtimes, extend the shipped, dependency-free .pth bootstrap with the same additive context behavior. This deliberately avoids the proposed merged-certifi temp file: a merged PEM cannot portably enumerate and retain Windows/macOS native roots, while the bootstrap can preserve native trust plus the extra CA without a temp-file lifecycle.
    5. Extend build_child_tls_env() so apm run and managed runtime subprocesses receive the normalized extra path as NODE_EXTRA_CA_CERTS, while preserving an explicit non-empty NODE_EXTRA_CA_CERTS. Apply that seam to both apm run subprocess branches. Rust/Codex keeps its runtime-owned trust configuration; the PR will not claim otherwise.
    6. Add hermetic coverage with independent loopback CAs: default trust remains valid, the extra private CA becomes valid only in additive mode, replacement variables remain replacement, disable remains highest precedence, malformed/missing/directory paths fail closed, Node env propagation is non-destructive, and a foreign Python venv verifies through the shipped bootstrap.
    7. Update the enterprise security model, SSL troubleshooting page, environment-variable reference, docs scope assertions, and Unreleased changelog entry.

    I also checked adjacent PR #2602. It touches the same TLS owner for diagnostics and already advertises this precedence, so I will rebase before opening and fold its diagnostic/isolation behavior in if it lands first.

  4. TameTheGame commented on Aug 31, 2026

    @TameTheGame

    Implementation-plan amendment after adversarial review:

    • I changed the child strategy to APM-owned per-process snapshots under ~/.apm/tls: an extra-only PEM for Node and a certifi+extra PEM for ordinary Requests children. This makes generic apm run Python children additive without depending on the managed bootstrap and makes source mutation after validation irrelevant. Managed Python runtimes still install the bootstrap so native trust can be retained when truststore is available.
    • The corrected precedence is REQUESTS_CA_BUNDLE > CURL_CA_BUNDLE > APM_DISABLE_TRUSTSTORE > APM_EXTRA_CA_BUNDLE > OS trust > certifi fallback. The disable switch suppresses APM-managed OS/additive propagation but intentionally does not unset an explicit replacement bundle.
    • If truststore injection is unavailable, the parent merged fallback is deliberately Requests-scoped. Parent stdlib urllib paths keep their normal fallback because they do not consume Requests bundle variables.

    The PR now exercises private and public roots together, child and grandchild propagation, native Node override preservation, source-bundle mutation after validation, and exact rollback after failed TLS publication.

  5. TameTheGame commented on Sep 7, 2026

    @TameTheGame

    Implementation update: the proposed implementation is in #2741, now at ec035c10.

    The latest follow-up removes redundant inner rollback, consolidates the parent fallback, and shares one HTTPS test server while preserving the additive trust and child-runtime behavior described above. It also corrects Windows CA-override and Bash test assumptions. The follow-up removes a net 93 lines across eight files.

    Local validation on Windows/Python 3.12.13:

    • 364 affected tests passed, with no skips or deselections, including all three Windows symlink tests. Two warnings remain in the unchanged lifecycle timeout test's subprocess-reader threads.
    • Ruff lint/format, Pylint duplication, architecture/auth checks, and the CI source guards passed.
    • Documentation built successfully: 124 pages and 1,007 relative links checked, with no broken links.

    The tests exercise real private-CA HTTPS, retention of an independent existing root, Python/Node child propagation, and recovery after an additive TLS context has actually been published. The independent-root proof uses synthetic loopback CAs; this pass did not exercise live public-Internet HTTPS, a complete private-registry apm install, a fresh packaged executable, or Linux/macOS execution.

    The PR validation section contains the current evidence and scope. The PR remains open; all six upstream workflows currently report action_required, pending repository approval to run.

  6. TameTheGame commented on Sep 7, 2026

    @TameTheGame

    The install acceptance gap identified in the review of #2741 is now covered in e9e0301a.

    The new regression executes the real source CLI's apm install against two independently signed loopback HTTPS registries. It proves that APM_EXTRA_CA_BUNDLE enables installation from the private-CA source while preserving an existing independent default root. Negative controls reject the private source without the extra CA and reject the default source when replacement-only trust excludes it. Successful installs verify package content, the lockfile, and deployed instructions.

    The default root is synthetic and seeded into the test process's certifi bundle; the test does not change the machine trust store, contact public services, or mock transport/resolution. This complements the existing real apm run, Python/Node child, and independent-context trust tests.

    Local validation: 373 affected tests passed, with no skips, including the Windows symlink tests. Two existing lifecycle timeout reader-thread warnings remain. Required lint and documentation checks passed. The revision also clarifies shell-inline controls and makes managed-bootstrap refresh failure visible.

    September 8 update: all six upstream workflows passed on e9e0301a, including CI and the separate CodeQL findings check, which reported no new alerts and zero annotations. The latest commit, 4b923664, only corrects the changelog format and PR reference; its source and tests are unchanged. The PR remains open and conflict-free. The changelog commit's newly triggered workflows require maintainer approval, and human review is still pending.

  7. danielmeppiel commented on Sep 12, 2026

    @danielmeppiel
    CollaboratorAuthor

    At the lead maintainer's direction, the current acceptance of this item is withdrawn as part of APM's roadmap and contribution reset. Previous acceptance labels or comments must not be treated as approval to start or continue substantive implementation for inclusion in APM.

    Thank you for the work and ideas already contributed. Our small maintainer team needs to agree supported scope and review capacity before inviting more implementation. This is not a rejection of the proposal or a judgment of your work, and it does not undo completed work or erase its history.

    Please follow the contribution rules: a responsible human maintainer needs to record the bounded scope, acceptance criteria and review contact on the issue before substantive work resumes. Existing issue discussion, assignments and contributions remain available for that decision. Trivial typo and broken-link corrections retain their standing preapproval.

  8. added
    triage/recommendedAutomated advice completed; not human scope approval.
    and removed
    status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.
    enhancementLegacy classification name; prefer type/feature. Retained for history.
    status/triagedAutomated advice completed; deduplication only. Not human approval; silence is not approval.
    on Sep 12, 2026
  9. TameTheGame commented on Sep 12, 2026

    @TameTheGame

    Understood. I’ll hold further work on #2741, including conflict repair, pending fresh scope approval and a review contact.

    Daniel Meppiel (@danielmeppiel) Sergio Sisternes (@sergio-sisternes-epam) — could one of you confirm whether the existing proposal is work the project can currently support?

    • Scope: opt-in APM_EXTRA_CA_BUNDLE support for APM’s Python HTTPS paths and Python/Node children, preserving explicit replacement and opt-out controls.
    • Acceptance: real apm install and apm run private-CA coverage, with independent existing-root retention and documented precedence/runtime limits.
    • Exclusions: Git/Rust trust configuration, machine trust-store changes, shell-command parsing, and the optional CA-directory setting.

    The implementation and requested review follow-ups are already in the PR. The functional revision e9e0301a passed upstream CI and the separate CodeQL findings check; the latest commit only changes the changelog.

    If this scope is still welcome, please record the fresh approval and review contact here under the current contribution process. If review capacity is unavailable, an explicit deferral would help us leave the work parked until it can be supported.

  10. danielmeppiel commented on Oct 2, 2026

    @danielmeppiel
    CollaboratorAuthor

    Thanks Josh Bazar (@TameTheGame) for the work in #2741 and for keeping it parked while the scope is reconsidered. We are revisiting this with npm/Node's additive-CA behavior as a concrete package-management precedent, rather than treating an undocumented deployment scenario as a reason to assume nobody is blocked.

    Node's official NODE_EXTRA_CA_CERTS documentation states that the normal root CAs "will be extended with the extra certificates in file." This is the additive route available to npm users; it is distinct from npm's ca/cafile settings, which select replacement trust. Node also documents that an explicit TLS ca option bypasses both normal and extra roots.

    Are you or your team currently blocked by this in an actual APM workflow? A redacted example of the command/error, operating system, corporate HTTPS proxy versus private registry, and why the CA cannot be installed into normal system trust would help us define the right acceptance case. Please distinguish APM's own HTTPS requests, native Git access, and apm run execution if more than one is involved. Please do not share credentials, private keys, or sensitive internal endpoints.

    Your reported real-CLI private-CA installation coverage in this update already helps establish the technical behavior; the question is about the deployment requirement, not asking you to repeat that work. A general compatibility contribution is welcome too, even if it is not a personal blocker.

    The product direction under discussion now favors equivalent additive trust for supported package-management HTTPS, retaining normal roots, certificate verification and explicit replacement/opt-out intent. Experimental execution-runtime propagation is a separate scope question; the intended apm run deprecation should not be used to dismiss a core installation need. Git's own TLS configuration also remains a distinct boundary.

    This is scenario clarification and product-direction feedback, not yet the fresh bounded scope approval required by CONTRIBUTING. No deferral is being applied, and no further implementation or conflict repair is requested before that record is agreed.

    Recorded by Copilot on behalf of Daniel Meppiel (@danielmeppiel) following his npm-parity and scenario clarification on 2026-10-02.

  11. danielmeppiel commented on Oct 2, 2026

    @danielmeppiel
    CollaboratorAuthor

    Decision: approve
    Area: project
    Scope: Add opt-in APM_EXTRA_CA_BUNDLE support for APM's own package-management HTTPS, including installation, registry access and downloads, retaining the normal default trust roots alongside the additional corporate PEM certificates. Preserve existing explicit replacement-bundle and truststore opt-out intent; keep certificate verification enabled and centralize trust decisions in the existing TLS authority.
    Done when: Real apm install against a private-CA HTTPS source succeeds with the additional bundle while an independently trusted default-root source remains usable; negative controls still reject untrusted certificates. Unset configuration preserves current behavior; invalid extra-bundle input produces a clear failure rather than silently discarding requested trust. Document and cover precedence with REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE and APM_DISABLE_TRUSTSTORE, update the TLS troubleshooting documentation and corresponding apm-usage guidance, and preserve supported existing TLS behavior.
    Out of scope: APM_EXTRA_CA_DIR; new propagation into experimental apm run Python/Node execution children; native Git/Rust TLS reconfiguration; changing machine trust stores; disabling certificate verification; unrelated TLS-framework redesign; blanket approval of the existing broader PR #2741.
    Review contact: Daniel Meppiel (@danielmeppiel)
    Reason: Recorded by Copilot on behalf of Daniel Meppiel (@danielmeppiel) following his explicit acceptance on 2026-10-02: "yes accept issue 2034 on the extra corproate certificates". The confirmed npm/Node additive-trust precedent supports this core package-management capability. The scenario follow-up at #2034 (comment) remains useful for acceptance coverage but is not a condition for product acceptance. Josh Bazar (@TameTheGame) is invited to align the existing contribution in #2741 with this bounded scope through the contribution process; do not start competing work or assume approval of its excluded runtime portion. This is product-scope approval, not a PR review, merge permission or authorization to add a ninth item to the separately delegated eight-issue autopilot run.

  12. TameTheGame commented on Oct 3, 2026

    @TameTheGame

    Updated #2741 in 73ae7920 to match the approved package-management scope.

    • APM_EXTRA_CA_BUNDLE adds corporate PEM certificates to APM's own HTTPS while retaining default roots, certificate verification, and explicit Requests/curl replacement and opt-out intent.
    • Removed the new Python/Node execution-child propagation and associated snapshot/refresh machinery. Existing runtime trust behavior is preserved; native Git/Rust configuration remains separate.
    • Real source-CLI install coverage now runs ten fresh-project cases across normal OS trust and forced truststore fallback, proving private-CA installation, independent default-root retention, and untrusted/replacement-only rejection. Additional checks cover stdlib metadata HTTPS fallback and unchanged child trust.
    • 363 affected tests passed with no skips, including elevated Windows symlink cases; 28 final focused checks and 64 repository quality checks passed. Required lint and documentation checks passed, and the PR is conflict-free.

    This remains a general compatibility contribution with hermetic acceptance evidence; I am not claiming validation in a particular enterprise deployment. The PR description links the current scope approval and records the full evidence. Hosted workflows await maintainer approval, followed by review from Daniel Meppiel (@danielmeppiel).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/docs-sitedocs/src/content (Starlight), README, doc generation.area/enterpriseAir-gapped/GHE configurability, registry proxy, rulesets, adoption playbook.priority/highHuman-set high priority; not scope approval, a release commitment or a required milestone.status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.theme/securitySecure by default. Content scanning, lockfile integrity, MCP trust boundaries.triage/recommendedAutomated advice completed; not human scope approval.type/featureNew capability, new flag, new primitive.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions