Most organisations that run Python for data science pull binaries from conda channels without ever deciding which channels they trust, who builds those packages, or which licence terms apply to the download. Conda resolves packages from an ordered list of channels set across several configuration files, so one stray -c flag or a user's ~/.condarc can quietly change where code comes from. This guide covers channel trust and priority, how conda-forge builds and publishes, locking environments for reproducible and reviewable installs, administrator controls in .condarc, and the commercial exposure created by Anaconda's repository terms. It is written for platform engineers, AppSec teams and data platform owners. For the wider data science picture (notebooks, datasets, model files) see the Security for Data Science Teams; this page goes deeper on conda only.
This is an independent guide from NHI Mgmt Group, based on conda's public documentation, conda-forge's documentation and Anaconda's published terms as of November 2026. It is not sponsored or reviewed by Anaconda or the conda-forge project. Product features and terms change, so confirm settings against current documentation.
Key takeaways
- Channels are your trust boundary. Every channel in the resolved list can supply code into the environment, so pick one primary source (defaults or conda-forge, as conda's own docs advise) and lock the list centrally.
- Strict priority closes a substitution gap. The default
channel_priorityisflexible;strictstops a lower-priority channel from supplying a package name that a higher-priority channel already carries. - conda-forge is community-built. Its own 2025 incident notice says the packages "are built by strangers on the internet" and are not suitable where secure provenance is required, so treat it like any public index and add your own controls.
- Lock and solve once. conda-lock produces per-platform lockfiles that install without a fresh solve, which turns dependency changes into reviewable diffs instead of runtime surprises.
- Licence exposure is a supply chain risk too. Anaconda's terms require a paid Business Plan for for-profit organisations above 200 employees and contractors, and count CI/CD jobs as users, so unmanaged access to repo.anaconda.com is a compliance finding, not just a cost question.
How conda packages reach your machines
Conda installs prebuilt binary packages, often bundling compiled C, C++ and Fortran libraries, from channels. A channel is a URL that serves repository metadata (repodata) and package files. That differs from pip in two ways that matter for security: packages ship native code with no build step on your side, and one environment can draw from several channels at once. See the Python Packaging and pip Security Guide for the pip side, which still applies because most conda environments also contain pip-installed packages.
| Source | Who builds and publishes | Default address | Key consideration |
|---|---|---|---|
Anaconda defaults | Anaconda, Inc. on its build network | https://repo.anaconda.com/pkgs/ | Covered by Anaconda's terms of service, including the paid-plan threshold |
| conda-forge | Community maintainers, built on public CI from GitHub feedstocks | conda.anaconda.org/conda-forge (via channel_alias) | Anyone can contribute a package; review happens through pull requests |
| Other community channels (for example bioconda) | Their own communities | Hosted on anaconda.org or elsewhere | Separate maintainers and build pipelines; mixing with conda-forge needs care over order |
| Personal or project channels on anaconda.org | Any account holder | conda.anaconda.org/<user> | No organisational review; the highest-risk source of ad hoc installs |
| Internal channel or repository proxy | Your platform team | Your own URL | Only source you fully control; must respect upstream licence and mirroring terms |
The channel_alias setting (default https://conda.anaconda.org) is the prefix conda adds to any bare channel name, so -c someuser resolves to an anaconda.org account. That is convenient for researchers and exactly why channel names typed from a blog post or README deserve the same suspicion as an unknown package name. The typosquatting and namespace patterns in the Malicious Package and Dependency Confusion Guide apply directly.
Channel priority and why it matters
Conda sorts candidate packages using channel order and the channel_priority setting. The configuration reference lists three values, strict, flexible (the default) and disabled.
| Setting | Resolver behaviour (per conda docs) | Supply chain effect | When to use |
|---|---|---|---|
strict | Packages from lower-priority channels are excluded when a higher-priority channel has the same package name | A package name can only come from the first channel that carries it, which blocks name substitution from a later channel | Recommended baseline; conda-forge also recommends it |
flexible | Prefers higher-priority channels, sorting by channel priority, then version, then build number, but can fall back | A later channel can still supply a package when the solver needs it | Legacy environments that do not solve under strict |
disabled | Newest version from any listed channel wins | Any channel in the list that publishes a higher version number wins, the dependency confusion pattern | Avoid in managed environments |
- One primary channel: conda's channel management page recommends configuring either
defaultsorconda-forgeglobally rather than both, because mixing builds can cause ABI incompatibility and "hard-to-debug runtime errors". - Per-package overrides: the
channel::packagesyntax (for exampleconda-forge::numpy) pulls one package from a named channel and resolves its dependencies from the configured list. Allow it in reviewed environment files, not in ad hoc commands. - Override flag:
--override-channelswith--channelignores the.condarclist for that command, so a locked list in configuration is only half the control; channel allowlisting (below) is the other half. - Base environment: conda-forge advises installing into a new environment rather than
base. Keepbasefor conda itself so a bad package does not break the tool used to recover.
Inside conda-forge: what the build pipeline does and does not give you
Each conda-forge package lives in a feedstock, a GitHub repository that is "the home of one recipe", created after a recipe passes review in the staged-recipes repository. Linux and noarch packages build in Docker containers on CI; macOS and Windows use tuned CI runner images. Almost all changes arrive as pull requests, including automated version bumps from conda-forge-bot, and maintainers or the core team merge them.
Uploads go through two checks. Local validation on CI compares produced artefacts against the feedstock-outputs repository (which records which feedstock may publish which package name), and uploads happen only from qualifying events such as commits to main, not pull requests. Server-side validation then repeats the check before packages move from the cf-staging channel to conda-forge, to guard against interference at feedstock level.
That design is a real control against one feedstock publishing another project's package name, but it is not provenance in the sense your auditors mean. The project's April 2025 security notice (CVE-2025-31484) described misconfigured infrastructure, found during an external audit, that exposed the conda-forge channel's anaconda.org upload token to all feedstock maintainers from about 10 February to 1 April 2025. The token was disabled within the hour of notification, uploads were audited for staging bypasses and none were found, though the team noted that check "is not completely robust". The same notice states that conda-forge packages are not suitable for use cases that require secure software provenance. The customer-side lesson is the same as for the Ultralytics PyPI Compromise 2024: a publishing token in CI is a single point of failure, so your own pinning, scanning and mirroring decide what reaches production.
Integrity checks conda performs, and their limits
| Setting | Default | What it does | Recommendation |
|---|---|---|---|
ssl_verify | true | Verifies TLS certificates; also accepts a CA bundle path, a certificate directory or truststore for the OS store | Never set false; use truststore or a CA path behind TLS-inspecting proxies |
safety_checks | warn | Enforce available safety guarantees during package installation | Set enabled on build agents and shared servers |
extra_safety_checks | false | Spends extra time validating package contents, verifying SHA-256 for every file in each package | Turn on for production images and CI builds where install time is acceptable |
signing_metadata_url_base | Unset | Base URL for trust metadata used by signature verification | See the note on conda-content-trust below before relying on it |
client_ssl_cert | Unset | Client certificate (and client_ssl_cert_key) for mutual TLS to a private channel | Prefer this or short-lived tokens over long-lived static channel tokens |
Anaconda designed conda signature verification on The Update Framework (TUF), with packages signed on its build network and root keys held offline. Its own page now states that, as of September 2026, conda-content-trust metadata signature verification is End of Support and "should not be relied on as a security control"; integrity rests on HTTPS, checksums in repository metadata and Anaconda's build and distribution controls. Plan on hash pinning and controlled channels as your integrity controls rather than client-side signatures.
Locking environments
An environment.yml with loose versions triggers a new solve every time it is installed, so two builds a week apart can contain different binaries. conda-lock runs the solve once per target platform and records the result, and installing from the lockfile does not invoke the solver.
- Formats: the unified
conda-lock.yml(multi-platform, the default), the explicit per-platform format that conda and mamba read natively (conda create --name ENV --file conda-linux-64.lock), and an environment-file rendering that re-solves on install and so gives up the locking benefit. - Pip inside conda: conda-lock integrates pip-solved packages, but only conda-lock itself can install those from the explicit format. Decide whether pip is allowed at all:
add_pip_as_python_dependencydefaults totrue, so pip arrives with every Python. - Review: commit lockfiles, require review on changes, and regenerate on a schedule (for example monthly) rather than whenever someone hits a solve error. The trade-offs are covered in Dependency Pinning vs Version Ranges.
- Inventory: a lockfile is also your software inventory for that environment; feed it to SBOM tooling. See the SBOM Guide.
Administrator controls in .condarc
Conda reads configuration from system locations (/etc/conda/, /var/lib/conda/ or C:/ProgramData/conda/), then $CONDA_ROOT, then user locations (~/.config/conda/, ~/.conda/, ~/.condarc), then the active environment, then $CONDARC. Later files supersede earlier ones, CONDA_* environment variables override all files, and command-line parameters win over everything. Lists and dictionaries merge; single values are replaced. That precedence means a central file only holds if you lock it.
- Restrict channels: set
allowlist_channelsto your approved list (the default empty list means no restriction).denylist_channelsentries override allowlist entries, which helps block personal anaconda.org channels. - Repoint defaults: use
default_channelsandcustom_channelsto senddefaultsand named channels to your internal repository, and setchannel_aliasso bare-c namerequests also resolve internally. - Lock with #!final: in the system
.condarcof the installation, append#!finalto a key so no other configuration source can overwrite it. The conda multi-user page showschannelsandchannel_aliaslocked this way, after which a search against an unlisted channel is refused as not in the allowed channels. - Set integrity defaults:
channel_priority: strict,safety_checks: enabled,extra_safety_checks: truein the same locked file. - Remember the gaps: users who install their own Miniforge or Miniconda in their home directory bypass a system file entirely. Pair configuration with endpoint software control or an egress rule that only permits your internal repository host.
Licence exposure: Anaconda's repository terms
Anaconda's terms of service (page dated 15 July 2025) allow free use for individuals for personal, non-commercial purposes, for eligible academic and non-profit research organisations, and for for-profit organisations with 200 or fewer total employees or contractors, including all affiliates. Above that, a for-profit organisation must buy a Business Plan subscription. The terms state that they apply when Miniconda is used to access the platform, and Anaconda may request proof of eligibility.
- Non-human users count: the terms FAQ defines users to include CI/CD jobs and other non-human clients, and counts as active any user that accessed artefacts from repo.anaconda.com or Anaconda-maintained repositories within the last 12 months. Build agents, container builds and notebook images are therefore in scope. Inventory them as non-human identities; see Human vs Non-Human Identity.
- Mirroring: the terms prohibit framing or mirroring the platform, directly or via a proxy, unless authorised by Anaconda. Check your agreement before pointing a repository proxy at repo.anaconda.com.
- conda-forge and the tool: the terms do not name conda-forge; they say third-party content is governed by its own licence, and refer anaconda.org use to separate terms. Many institutions have moved to Miniforge, which sets conda-forge as "the default (and only) channel". Moving channel is a supply chain change in its own right: rebuild and retest environments, because packages differ between channels.
Treat this as a joint task for procurement and engineering, not legal alone; the Open Source Licence Compliance Guide covers package-level licences, which still apply whichever channel you use.
Choosing a model
| Model | Suits | Controls you must add |
|---|---|---|
| Licensed Anaconda repository, defaults only | Organisations above the threshold wanting a vendor-built channel and support | Locked channel list, strict priority, licence inventory of human and CI users |
| conda-forge only via Miniforge | Research groups and smaller teams needing breadth of packages | Lockfiles, malware and vulnerability scanning, delayed adoption of new releases, no personal channels |
| Internal repository proxying approved upstreams | Large or regulated organisations | Upstream terms review, quarantine period for new packages, single egress path, access tokens for the proxy managed as secrets |
| Curated internal channel built from source | High-assurance and air-gapped environments | Own build pipeline, signing and provenance; highest effort, smallest package set |
Small teams usually do best with one channel, lockfiles and the locked .condarc; the proxy and curated models pay off once several hundred users or regulated workloads are involved.
Where identity fits
Private channels and repository proxies are reached with tokens embedded in channel URLs, stored by anaconda login, or set in CI variables. Those tokens are non-human credentials: they end up in .condarc files committed to repositories, in Docker layers and in notebook outputs. Scope proxy tokens to read-only, rotate them, keep them out of images (pass them at build time as secrets), and scan repositories for them. Publishing tokens for internal channels need the tightest handling; see the PyPI API Tokens and Trusted Publishing Security Guide for the equivalent pattern and the Artifactory Tokens Security Guide if your proxy runs there.
How to implement
- Discover (weeks 1 to 2): find conda installations on endpoints, servers and images; collect every
.condarcandenvironment.yml; list channels in use and every host that reaches repo.anaconda.com or conda.anaconda.org (proxy logs are the fastest source). - Decide: choose a model from the table above and confirm the licence position for your headcount and CI users.
- Pilot (one team, two weeks): deploy the locked system
.condarcwith strict priority and an allowlist in audit-friendly form first (log refusals, keep a documented exception path), convert two key environments to conda-lock and build from lockfiles in CI. - Roll out: push the configuration through your endpoint and image tooling, add the egress rule, and move CI to lockfile installs.
- Test: confirm a
-cto an unlisted channel is refused, that--override-channelscannot reach a blocked host, and that lockfile installs are byte-identical across two builds. - Roll back: keep the previous configuration and lockfiles versioned; removing the
#!finalkeys restores prior behaviour, and a failed solve under strict can fall back toflexiblefor that environment only, recorded as an exception.
Detection and measurement
- Egress logs: alert on requests to conda.anaconda.org paths outside your approved channel names and on any direct traffic to repo.anaconda.com when you route through a proxy.
- Config drift: check images and hosts for
ssl_verify: false,channel_priority: disabledand user-level channel lists. - Repository logs: new package names requested for the first time, and spikes in downloads from service accounts.
| Metric | Target |
|---|---|
| Production environments built from a committed lockfile | 100% |
Hosts and images with the locked system .condarc | Above 95% |
| Requests to unapproved channels per month | Falling to near zero after rollout |
| Median age of lockfiles in production | Under 90 days |
| CI and human users of repo.anaconda.com mapped to a licence position | 100% |
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | ISO/IEC 27001:2022 Annex A | CIS Controls v8.1 |
|---|---|---|---|---|
| Approved channels and allowlisting | CM-7 Least Functionality, SR-3 Supply Chain Controls and Processes | GV.SC-05 | A.8.19 Installation of software on operational systems | Control 2 Inventory and Control of Software Assets |
| Lockfiles and inventory | CM-8 System Component Inventory, SR-4 Provenance | ID.AM-02 | A.5.21 Managing information security in the ICT supply chain | Control 2 |
| Integrity checks | SR-11 Component Authenticity, CM-14 Signed Components | PR.PS-02 | A.8.19 | Control 16 Application Software Security |
| Licence compliance | SA-15 Development Process, Standards, and Tools | GV.SC-05 | A.5.32 Intellectual property rights | Control 2 |
The threat these controls address is MITRE ATT&CK T1195.001 Compromise Software Dependencies and Development Tools.
Common failure patterns
- Mixed channels without strict priority: defaults and conda-forge both listed with flexible priority, giving unpredictable sourcing and ABI breakage.
- Personal channels in production: a README's
-c someusercopied into a Dockerfile, so an individual anaconda.org account becomes a production dependency. - Configuration that does not hold: a system
.condarcwithout#!final, or users running their own installer, so the central policy is advisory only. - Disabling TLS checks:
ssl_verify: falseset to get past an inspecting proxy and then baked into a base image. - Licence surprise from CI: build agents and container builds pulling from repo.anaconda.com without anyone counting them as users.
- Tokens in channel URLs: private channel tokens committed in
.condarcor left in image layers, the same failure behind the PyPI Admin GitHub Token Leak 2024.
Practitioner checklist
- Pick one primary channel and record the decision and its licence basis.
- Set
channel_priority: strict,safety_checks: enabledandextra_safety_checks: truecentrally. - Enforce
allowlist_channelsand lock keys with#!finalin the system.condarc. - Point
channel_alias,default_channelsandcustom_channelsat your internal repository if you run one. - Build every production environment from a committed conda-lock file.
- Restrict egress to approved repository hosts so private installers cannot bypass policy.
- Inventory CI jobs and humans that reach repo.anaconda.com against Anaconda's terms.
- Treat channel and proxy tokens as secrets: read-only scope, rotation, scanning.
- Subscribe to conda-forge and Anaconda security notices and rebuild when advisories land.
Standards and references
- conda: Configuration reference
- conda: Managing channels
- conda: Administering a multi-user conda installation
- conda-forge: Feedstocks
- conda-forge: Security incident with package uploads
- conda-lock: Output formats
- Anaconda: Terms of Service
- Anaconda: Conda signature verification
Related NHI Mgmt Group resources: Software Supply Chain Security Guide, Security for Data Science Teams, Python Packaging and pip Security Guide, Malicious Package and Dependency Confusion Guide.