> ## Documentation Index
> Fetch the complete documentation index at: https://tally.wharflab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# tally/ruby/prefer-secret-mounts-for-build-credentials

> Build-time credential declared via ARG/ENV; prefer `RUN --mount=type=secret,...`.

A Ruby-shaped Dockerfile declares a build-time credential (Bundler host token, NPM/Yarn auth, RubyGems API
key) via `ARG` or `ENV`. The credential ends up in image cache key data and image history. BuildKit's
secret-mount syntax is the supported alternative.

| Property | Value                     |
| -------- | ------------------------- |
| Severity | Info                      |
| Category | Security                  |
| Default  | Enabled (advisory)        |
| Auto-fix | `FixSuggestion` (no-edit) |

## Description

Build-time credentials passed via `ARG` or `ENV` end up in two places that the user generally doesn't want:

* **Image cache key data.** `ARG`s become part of the cache key BuildKit uses to deduplicate build steps.
  Different secret values produce different cache keys, leaking the credential into anyone with read access
  to the cache.
* **Image history.** Once the credential is consumed via `RUN` or referenced from `ENV`, it ends up in
  `docker history --no-trunc <image>` for anyone who can pull the image.

BuildKit's `RUN --mount=type=secret,id=<id>,env=<VAR>` is the documented production pattern: the secret is
exposed only inside the one `RUN` and never reaches the image cache key or layer content.

This is the **constructive companion** to
[`tally/ruby/secrets-in-arg-or-env`](/rules/tally/ruby/secrets-in-arg-or-env/), which catches the same class
of issue for Rails-app secrets at error severity. This rule covers Ruby-ecosystem build-time credentials
specifically:

* `BUNDLE_GITHUB__COM`, `BUNDLE_BITBUCKET__ORG`, `BUNDLE_GITLAB__COM` — Bundler host tokens.
* `BUNDLE_<HOST>__<TLD>` — the generic Bundler host-credential pattern (host names use `__` for `.`).
  Compound TLDs are supported (`BUNDLE_GEMS__ACME__CO__UK`). Bundler's non-host config namespaces
  (`BUNDLE_LOCAL__<gem>`, `BUNDLE_BUILD__<gem>`) are excluded because they encode gem identity rather
  than a hostname.
* `GEM_HOST_API_KEY` — RubyGems push key.
* `NPM_TOKEN`, `YARN_AUTH_TOKEN` — npm/yarn auth tokens, commonly used during `bin/rails assets:precompile`
  to fetch private packages.

The corpus shows only 1 of 196 Dockerfiles using `RUN --mount=type=secret`. This rule's job is to surface
the supported alternative at the moment a user reaches for ARG/ENV.

The rule only fires on Ruby-shaped Dockerfiles — non-Ruby Dockerfiles that happen to use one of these env-var
names (e.g. a Node.js Dockerfile with `NPM_TOKEN`) are ignored. A meta-`ARG` (declared before any `FROM`)
also requires at least one Ruby stage somewhere in the file.

Stages explicitly named `dev`/`development`/`test`/`testing`/`ci`/`debug` are skipped.

## Examples

### Before

```dockerfile theme={null}
FROM ruby:3.3-slim
ARG BUNDLE_GITHUB__COM
ENV BUNDLE_GITHUB__COM=$BUNDLE_GITHUB__COM
RUN bundle install
```

### After

```dockerfile theme={null}
FROM ruby:3.3-slim
RUN --mount=type=secret,id=github_token,env=BUNDLE_GITHUB__COM \
    bundle install
```

Pass the secret at build time:

```bash theme={null}
docker buildx build --secret id=github_token,src=$HOME/.netrc-github .
```

The secret exists for the duration of that one `RUN` and never reaches image content or cache key data.

## Auto-fix

`FixSuggestion` (no-edit). The exact rewrite depends on the user's CI / vault / secret-injection setup, so
the rule cannot auto-apply. Description names the canonical form including the `secret_id` it suggests.

## References

* [Bundler `bundle config` documentation](https://bundler.io/v2.5/man/bundle-config.1.html#CREDENTIALS-FOR-GEM-SOURCES) —
  describes the `BUNDLE_<HOST>__<TLD>` env-var convention.
* [BuildKit `RUN --mount=type=secret` documentation](https://docs.docker.com/build/building/secrets/).
* [`basecamp/fizzy/saas` Dockerfile](https://github.com/basecamp/fizzy) — production example using
  `RUN --mount=type=secret,id=GITHUB_TOKEN ... BUNDLE_GITHUB__COM=...`.
