Skip to main content

Protect your Launchpad API tokens with a security key

Supply-chain attacks that harvest developer credentials are on the rise. The Shai-Hulud campaign, for example, used automated scanners to sweep up any secrets they could find in files and keyrings. If you use Launchpad’s API, one of those secrets is your API token – and until now it has been about as safe as the laptop it sits on.

Launchpad now supports hardware-bound API tokens: you can tie your API access to a USB FIDO2 security key, such as a YubiKey, so that a stolen token is useless to an attacker who does not have your physical key.

The problem

Tools like lp-shell and ubuntu-archive-tools use the launchpadlib library to talk to Launchpad. After you log in, launchpadlib stores your OAuth token – either as a plaintext file on disk or in your desktop keyring. The keyring is unlocked when you log in to your session, so to anything running as you, or to malware that can read your files, that token is effectively plaintext.

For most people that is an acceptable risk. For core developers, stable release update team members, archive administrators, and Launchpad developers who run a lot of unaudited code, it is a tempting target.

The idea

Hardware-bound API tokens change what gets stored. Instead of a usable token, launchpadlib stores a long-lived OAuth refresh grant that is useless on its own. To do anything with it, launchpadlib has to ask your security key to sign a challenge from Launchpad, which mints a short-lived access token for the actual API calls.

The stored grant cannot mint a token without the key. So if an attacker copies your grant, they get nothing – the signing happens on a device they do not have.

How it works

Once you have registered a key and issued a grant, logging in looks like this:

  1. launchpadlib loads your stored refresh grant.
  2. It asks Launchpad for a challenge.
  3. Your security key signs the challenge (you touch the key, and enter its PIN if it has one).
  4. Launchpad verifies the signature against your registered key and returns a short-lived access token.
  5. launchpadlib uses that token for API calls until it is close to expiry, then you need to touch your hardware key to renew.

The signing step is a standard WebAuthn assertion – the same mechanism browsers use for passkeys – so it builds on a well-tested security model.

Keeping it usable

Security that gets in the way does not get used, so the design keeps everyday work smooth:

  • A minted access token is reused until it is nearly expired, so you are not touching your key on every command.
  • Long-running scripts keep working; the token is renewed as needed.
  • The grant is stored in your keyring by default, or in a file you control if you prefer.

What it protects – and what it doesn’t

Being honest about the boundaries matters:

  • Opportunistic theft of stored credentials – the main thing this defends against. A scanner that grabs your grant gets something it cannot use.
  • Targeted attacks on your user session – if an attacker can already run code as you, they could still capture a freshly minted access token. Reducing that exposure is future work.
  • Root-level compromise – out of scope. If an attacker owns the machine, no client-side measure fully protects you.

This is also specifically about programmatic access through launchpadlib, not logging in to the Launchpad website, and it is not intended for automation or bot accounts.

Try it

You will need a USB FIDO2 security key and launchpadlib with the fido2 extra:

pip install 'launchpadlib[fido2]'

Then register your key, issue a grant, and log in with it. The step-by-step instructions are in the Launchpad documentation’s how-to, Use hardware-bound API tokens, with the design and protocol covered in the developer docs.

We would love your feedback as you start using it! feedback@launchpad.net