Technical Briefs
Focused technical analysis documents covering specific engineering problems, proposed solutions, and recommendations
Overview
This section contains technical briefs — concise, structured analyses of specific engineering problems. Each brief examines the current state, identifies the problem, evaluates multiple solutions, and provides a ranked recommendation with effort estimates.
Browse Technical Briefs
Navigate through the sidebar to explore individual briefs, or use the search feature to find specific topics.
1 - Private Package Registry Migration
Analysis and recommendations for migrating from Gemfury to a more secure and cost-effective private package registry
Executive Summary
Hut 42 currently uses Gemfury as its private package registry, hosting 1,759 package versions across 140 packages for both Python (pip) and JavaScript (npm). Three converging issues — a long-lived
unrotated authentication token creating a dependency confusion security vulnerability, an accidental account tier change that increased costs by 25×, and architectural friction with the shift to AWS
serverless infrastructure — make migration away from Gemfury the right course of action.
This brief analyses the current setup, expands on the security risk, evaluates candidate replacement solutions with pros and cons, and provides a ranked recommendation with a high-level migration
approach and effort estimate.
Current State
| Property | Detail |
|---|
| Provider | Gemfury (team account) |
| In use since | 2019 |
| Package types | Python (pip / PyPI), JavaScript (npm) |
| Package count | 140 packages, 1,759 versions |
| Consumers | Heroku apps, AWS Elastic Beanstalk, AWS Lambda, GitHub Actions CI builds |
| Publishers | GitHub Actions (automated library build and push) |
| Auth mechanism | Long-lived static token (FURY_AUTH) stored as environment variable |
| Scope | Mixed Adrian Flux and Hut 42 packages on a single account |
How packages are currently consumed (Python)
--index-url https://pypi.org/simple/
--extra-index-url https://${FURY_AUTH}:@pypi.fury.io/hutfortytwo/
Problem Statement
1. Unrotated authentication token and dependency confusion risk
The FURY_AUTH token has not been rotated since the account was created. Any leakage of this token — through a repository, a log, an environment dump, or a compromised CI runner — would expose the
names of all private packages hosted in the registry.
Once an attacker knows a private package name, they can exploit a well-documented class of vulnerability known as dependency confusion (also called a namespace confusion or substitution attack):
- The attacker publishes a package to the public PyPI registry using the same name as a private package, but with a higher version number (e.g.
99999.0.0). - pip, when configured with
--extra-index-url, does not treat the primary index as authoritative. It queries all configured indexes simultaneously and selects whichever index returns the
highest version number — regardless of which index was listed first. - The malicious public package is installed silently in place of the private one, and its code executes within the application build or runtime.
This attack has been demonstrated at scale against Microsoft, Apple, PayPal, and others (Alex Birsan, 2021). The severity is high: code execution occurs during the build process, potentially
compromising secrets, credentials, and deployed application behaviour.
Why flipping the index order did not solve this
Reversing the configuration to make Gemfury the primary index:
--index-url https://${FURY_AUTH}:@pypi.fury.io/hutfortytwo/
--extra-index-url https://pypi.org/simple/
was trialled and reverted. The reason: Gemfury does not operate as a proxy or mirror of PyPI. It only hosts packages explicitly published to it. When pip resolves transitive dependencies of
private packages (e.g. django, requests, lxml), it queries the primary index first. Because Gemfury has no knowledge of these public packages, resolution fails. Hosting all public dependencies
in Gemfury is not viable given the combinatorial explosion of versions and transitive trees.
The fundamental issue is that Gemfury requires two separate index URLs, and pip’s behaviour with multiple indexes provides no safe ordering guarantee against dependency confusion.
2. Account tier and pricing
The account was inadvertently migrated from an individual plan (flat monthly fee) to a team plan (charged per package). With 140 packages, this has resulted in a 25× cost increase. Downgrading
from a team account back to an individual account is not possible on Gemfury. The only way to return to flat-rate pricing on Gemfury would be to create a new individual account and re-publish all
1,759 package versions — which constitutes a non-trivial migration effort in itself, without solving the security problem.
3. Architectural misalignment
Application development strategy is shifting towards serverless AWS using CloudFormation for infrastructure as code. Gemfury sits outside the AWS ecosystem, requiring static credentials and custom
integration at every build boundary. A registry within the AWS ecosystem would integrate natively with IAM roles, instance profiles, and GitHub Actions OIDC — eliminating long-lived credentials
entirely.
Requirements
| Requirement | Priority |
|---|
| Eliminate dependency confusion risk for pip installs | Critical |
| Remove long-lived static tokens as the auth mechanism | Critical |
| Separate Adrian Flux and Hut 42 package namespaces | High |
| Support both pip (Python) and npm (JavaScript) | High |
| Minimal disruption to existing applications (env var and build config changes acceptable; code changes not) | High |
| Support GitHub Actions for both publishing and consuming packages | High |
| Support Heroku, AWS Lambda, and Elastic Beanstalk build environments | High |
| Align with AWS serverless / CloudFormation strategy | Medium |
| Cost reduction from current team tier pricing | Medium |
| Package build and publish workflow redesigned for new registry | High |
Scope of Work
This body of work covers Adrian Flux applications only. It is assumed that every application uses at least one private package.
| Work Area | Description |
|---|
| Analysis | Document current package inventory, consumers, and build pipeline integrations |
| Solution design | Produce detailed design for chosen solution including auth strategy per platform |
| Package publishing pipeline | Redesign and implement GitHub Actions workflows to build and publish packages to new registry |
| Consumer migration | Update all package install configurations across Heroku, Lambda, Beanstalk, and GitHub Actions |
| Prototyping | Validate one representative application per platform type before batch rollout |
| Rollout | Migrate all applications in batches following successful prototypes |
| Decommission | Retire Gemfury account or transfer to Hut 42 scope only |
Migration Approach
Regardless of the solution chosen, the recommended approach is prototype-first, then batched rollout — one representative application per platform type is migrated and validated before the broader rollout begins.
Phase 1: Analysis and design (pre-migration)
- Audit all 140 packages: identify which are actively used, which are pip vs npm, and which applications consume them
- Document all consumers by platform type (GitHub Actions, Heroku, Lambda, Beanstalk)
- Evaluate and select replacement registry solution; produce detailed design including auth strategy per platform
- Define namespace/account separation for Adrian Flux vs Hut 42 packages
Phase 2: Infrastructure setup
- Provision new registry infrastructure
- Configure upstream/proxy connections as required by chosen solution
- Set up authentication mechanisms for each platform type
- Define infrastructure as code where applicable
Phase 3: Package publishing pipeline
- Redesign GitHub Actions publish workflows to build and push to new registry
- Run parallel publish to both Gemfury and new registry during transition period
- Validate all 140 packages are available in new registry
- GitHub Actions: Update one CI workflow to install from new registry
- Heroku: Update one Heroku app build configuration to use new registry
- AWS Lambda: Update one Lambda build/deploy pipeline to use new registry
- Elastic Beanstalk: Update one Beanstalk app to use new registry
Each prototype should be validated in a staging environment before production cutover.
Phase 5: Batched rollout
- Following successful prototypes, roll out changes to all applications in batches grouped by platform type
- Maintain Gemfury as fallback until all applications are confirmed migrated
Phase 6: Decommission
- Stop parallel publishing to Gemfury
- Remove
FURY_AUTH from all environments - Either close Gemfury account or transfer remaining Hut 42 packages to a separate Hut 42 account
Risks
Risks of proceeding with migration
| Risk | Likelihood | Impact | Mitigation |
|---|
| Build failures during transition | Medium | High | Prototype per platform before batch rollout; maintain Gemfury fallback |
| Heroku build complexity (auth at build time) | Medium | Medium | Spike early in Phase 4; consider custom buildpack if standard approach is insufficient |
| Auth token refresh adds build step complexity | Low | Low | Standardise a reusable step or composite action in GitHub Actions |
| Missed consumer applications | Medium | Medium | Full audit in Phase 1; monitor for Gemfury traffic after cutover |
| Registry auth misconfiguration blocking installs | Low | High | Test in isolated environment; validate each platform type in staging first |
Risks of doing nothing (or minimal action)
| Risk | Likelihood | Impact | Mitigation |
|---|
FURY_AUTH token leaked; attacker maps private package names | Medium | High | Token rotation is the minimum action; does not resolve the structural issue |
| Dependency confusion attack installs malicious package in a build | Low (but increasing) | Critical | Only mitigated by moving to a single-index architecture (CodeArtifact) |
| Continued 25× cost overrun on Gemfury team tier | Certain | Medium | Requires migration regardless of security decision |
| Gemfury pricing or availability changes again | Unknown | High | Reduces over time as migration progresses |
Minimum safe action if full migration cannot be resourced immediately: rotate FURY_AUTH and update it across all environments. This reduces exposure from a compromised token but does not address
the dependency confusion vulnerability.
Effort Estimate
A high-level effort estimate is required for each proposed solution. Estimates should be provided per phase and cover the following areas:
| Phase | Area to estimate |
|---|
| Phase 1 | Analysis and design — package audit, consumer mapping, solution design |
| Phase 2 | Infrastructure setup — registry provisioning, auth configuration |
| Phase 3 | Package publishing pipeline — GitHub Actions redesign and validation |
| Phase 4 | Prototyping — one representative application per platform type (GitHub Actions, Heroku, Lambda, Beanstalk) |
| Phase 5 | Batched rollout across all applications |
| Phase 6 | Decommission and cleanup |
| Total | Overall estimate including buffer for testing and review |
Note that the Heroku integration is likely to be the most uncertain item, as build-time authentication mechanisms vary by solution. This should be identified as a spike in Phase 4 and flagged if it represents a significant risk to the overall estimate.
Status
Current Status: Draft
History:
- 2026-03-10: Initial brief created