When you publish version 1.0 of a gem with gem push, how do RubyGems API keys, MFA and --otp work, and which gemspec metadata hardens the release?
answer
- gem signin writes a credentials file
- push_rubygem scope
- --otp or GEM_HOST_OTP_CODE
- rubygems_mfa_required = "true"
- allowed_push_host
basics
~20 sgem push uploads a built .gem using an API key from gem signin or GEM_HOST_API_KEY. With MFA enabled, the server demands a one-time code passed via --otp or GEM_HOST_OTP_CODE. Metadata rubygems_mfa_required and allowed_push_host harden pushes.
solid answer
~40 s`gem signin` creates an **API key** on rubygems.org, with scopes such as `push_rubygem` or `yank_rubygem`, and stores it in the credentials file (`~/.gem/credentials`); `GEM_HOST_API_KEY` overrides it, and `--key NAME` picks a named key. `gem push hue_palette-1.0.0.gem` sends the file with that key. If the account uses **MFA**, the server rejects the request and `gem` asks for a one-time code, or shows a WebAuthn URL; `--otp 123456` or `GEM_HOST_OTP_CODE` supplies it up front, and push, yank, owner and signin all accept it. In the gemspec, `metadata["rubygems_mfa_required"] = "true"` makes rubygems.org require MFA for pushes of this gem, and `allowed_push_host` makes `gem push` refuse any other server.
code
ruby · 7 linesGem::Specification.new do |spec|
spec.name = "hue_palette"
spec.version = "1.0.0"
# ...
spec.metadata["rubygems_mfa_required"] = "true"
spec.metadata["allowed_push_host"] = "https://rubygems.org"
endgo deeper
Recall the two steps, gem build then gem push, and that pushing needs an API key saved by gem signin.
Explain where the API key comes from, what scopes are, how MFA prompts for a code and how --otp or GEM_HOST_OTP_CODE supplies it.
Harden a release: MFA on the account plus rubygems_mfa_required, narrowly scoped keys, allowed_push_host for private gems and several owners.
Decide who may publish a team's gems and from where, balancing personal MFA-gated pushes against automated releases and key custody.
## The publishing path Publishing a gem is two commands: `gem build` writes `hue_palette-1.0.0.gem`, and `gem push hue_palette-1.0.0.gem` uploads it to the push server (rubygems.org unless told otherwise), which adds it to the index so `gem install hue_palette` can find it. Everything interesting is in **how the push is authorised**. ## API keys and scopes `gem push` authenticates with an **API key**, looked up in this order: 1. the `GEM_HOST_API_KEY` environment variable; 2. a named key chosen with `-k, --key NAME` from the credentials file; 3. a key stored for that specific host; 4. the default rubygems.org key. If there is none, `gem push` signs you in first. Signing in asks for your username or email and password and creates a new API key with **scopes**: - `index_rubygems` and `push_rubygem` are offered by default; - `yank_rubygem`, `add_owner`, `remove_owner` and `access_webhooks` can be added; - `show_dashboard` is an exclusive scope: a key that has it gets no other scope. The key is written to the **credentials file**, `~/.gem/credentials` (or the XDG data directory when that file does not exist); `gem env credentials` prints the path. If a key lacks the scope a command needs, `gem` says the existing key doesn't have that access and asks you to sign in again to add the scope. ## MFA and --otp When the account has **multi-factor authentication** enabled, the server answers a push, yank, owner change or sign-in with an unauthorised response that starts `You have enabled multifactor authentication`. `gem` then: - prints `You have enabled multi-factor authentication. Please enter OTP code.` and waits for a code from your authenticator app, or - when a security key is registered, prints a **WebAuthn** URL to visit and polls until you have verified. To avoid the prompt, pass the code up front: - `gem push hue_palette-1.0.0.gem --otp 123456` - or `GEM_HOST_OTP_CODE=123456 gem push hue_palette-1.0.0.gem` The `--otp` option exists on `gem push`, `gem yank`, `gem owner` and `gem signin`. Codes are short-lived, so a script that needs one is really an interactive release. ## Metadata that hardens a gem Two gemspec `metadata` keys change what publishing allows: | Key | Effect | |---|---| | `rubygems_mfa_required` = `"true"` | rubygems.org requires MFA for pushes of this gem, so every owner must pass a second factor | | `allowed_push_host` = a URL | `gem push` pushes there by default and refuses any other host | `allowed_push_host` protects against the opposite accident as well: a private gem whose gemspec names an internal server cannot be pushed to rubygems.org by mistake, because `gem push` stops with `... is not allowed by the gemspec, which only allows ...`. For a public gem, set it to `https://rubygems.org` or leave it out; the placeholder that `bundle gem` generates must not be left as is. ## Owners and automation - **Owners.** `gem owner hue_palette --add USER` and `--remove USER` manage who may push; these calls also need the matching `add_owner` or `remove_owner` scope and an MFA code when MFA is on. - **Several owners** mean a release does not depend on one person's account, and each owner's account should have MFA. - **Automated pushes** read the key from `GEM_HOST_API_KEY`, which a CI system stores as a secret. Give that key only `push_rubygem`, and remember that `rubygems_mfa_required` will then refuse pushes that cannot supply a code; keyless publishing from CI is a separate mechanism with its own setup. ## Habits for a 1.0 release - Enable MFA on the account and set `rubygems_mfa_required`, so a leaked API key alone cannot publish. - Keep API keys narrow: a key used for pushing does not need `add_owner`. - Never commit the credentials file or put `GEM_HOST_API_KEY` in a repository; treat it like a password. - Push from a clean checkout and a freshly built `.gem`, after inspecting its file list. - Add co-owners with `gem owner hue_palette --add ...` so a release does not depend on one person's account.
- What happens if you push without --otp while MFA is enabled?The first request is rejected with an unauthorised response saying multifactor authentication is enabled. `gem` then prompts `Please enter OTP code.`, or shows a WebAuthn URL when a security key is registered, and retries the request with the code. Only non-interactive runs fail outright, which is why scripts pass `--otp` or `GEM_HOST_OTP_CODE`.
- What does allowed_push_host protect against for an internal gem?Publishing it to the public rubygems.org by mistake. With `allowed_push_host` set to the internal server, `gem push` targets that server by default and refuses any other host with an error saying the host is not allowed by the gemspec, so a mistyped command cannot leak private code.
An API key is the house key and the OTP is a fresh code shown on the owner's phone for every delivery: a copied key still cannot open the door. rubygems_mfa_required is a sign on the door saying no delivery without the code, whoever holds a key.
saying these in an interview costs you the question
- Believes a leaked API key cannot publish because the password is separate
- Thinks --otp is an option of gem build
- Commits ~/.gem/credentials or a GEM_HOST_API_KEY value to a repository
- Thinks allowed_push_host only adds a host rather than restricting pushes
- Assumes rubygems_mfa_required affects gem install for users