Skip to content

feat(sbom): read build secrets in packages env values - #317

Merged
alexey-igrychev merged 1 commit into
fix/sbom/packages-env-reaches-pmfrom
feat/sbom/packages-env-secret-refs
Sep 11, 2026
Merged

alexey-igrychev merged 1 commit into
fix/sbom/packages-env-reaches-pmfrom
feat/sbom/packages-env-secret-refs

Conversation

@alexey-igrychev

@alexey-igrychev alexey-igrychev commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

There is no way to pass a mounted build secret to a package manager: packages[].env values are literal text, and the packages stage is deliberately declarative, so a shell substitution is not an option. A value can now reference a secret declared in the secrets section, and the reference is resolved while the package manager runs.

secrets:
  - id: GOPROXY
    env: GOPROXY
  - id: CI_JOB_TOKEN
    env: CI_JOB_TOKEN
packages:
  - type: go-mod
    workdir: /app
    env:
      GOPROXY: "%secret:GOPROXY%"
      GOPRIVATE: git.example.com/*
      GIT_CONFIG_KEY_0: 'url.https://gitlab-ci-token:%secret:CI_JOB_TOKEN%@git.example.com/.insteadOf'
      GIT_CONFIG_VALUE_0: 'https://git.example.com/'
      GIT_CONFIG_COUNT: "1"

What

  • BREAKING: a packages[].env value that literally contains %secret:<id>% or %secret_path:<id>% is now expanded instead of passed through. Any other %...% sequence — %2F, %H:%M, %image% — stays literal, so no other value changes.
  • %secret:<id>% in a packages[].env value expands to the secret's contents, without trailing newlines, and may sit inside a larger value.
  • %secret_path:<id>% expands to /run/secrets/<id>, for tools that take a path (DOCKER_CONFIG, AWS_SHARED_CREDENTIALS_FILE).
  • A reference to a secret missing from the secrets section fails the build with packages[0].env["GOPROXY"] references secret "MISSING", which is not declared in the secrets section, and a malformed id with invalid secret id "my token" — both during configuration parsing, before any stage runs. VERIFIED: real build, see the comment.
  • The generated command contains the mount path only, never the secret value, so the secret does not enter the stage digest, the build log or the image. VERIFIED: real build, see the comment.
  • Deliberately not included: referencing another packages[].env key or a build-container variable. werf.yaml is rendered as a Go template, so composing ordinary values is already possible without a runtime mechanism.

Why

The declarative packages section is the only stapel stage with network access, which is exactly why it must not accept shell. Reading a secret is the one capability that a template cannot provide, because the value does not exist at render time — so it needs a syntax of its own rather than a hole in the escaping.

The %name% form is the placeholder syntax werf already has: %image%, %image_slug%, %image_safe_slug%, %image_content_based_tag% and %project% in --add-custom-tag, --use-custom-tag, werf export --tag and imageSpec labels. Only the two known prefixes expand and everything else stays literal, exactly as those placeholders behave, so no escape mechanism is needed and existing values keep working.

${...} and $(...) forms were rejected: they read as shell in a place where shell does not run, and a value that resembles a mounted path (/run/secrets/<id> treated as a reference) cannot express "pass this path as text", which %secret_path:<id>% does explicitly.

@alexey-igrychev

alexey-igrychev commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Verification

  • Real build, Docker backend on macOS, registry.werf.io/base/golang:1.12-alpine3.9 base, secrets: [{env: TOKEN}] and env: {GOPROXY: "%secret:TOKEN%"} with TOKEN=http://127.0.0.1:1/: go dialed the secret's value — Get http://127.0.0.1:1/example.com/missing/@v/v1.0.0.info: dial tcp 127.0.0.1:1: connect: connection refused.
  • Real build with %secret:MISSING%: Error: unable to load werf config: packages[0].env["GOPROXY"] references secret "MISSING", which is not declared in the secrets section, before any stage ran.
  • Mutation: dropped the validatePackageEnvValues call from toStapelImageBaseDirective → the undeclared secret and malformed secret id entries of validates packages env secret references against the secrets section failed; reverted, suite green. That table drives the real entry point (toStapelImageDirective), so it pins the call site, not just the function.
  • Mutation: dropped the declared-id check inside validatePackageEnvValuesundeclared secret failed. Mutation: made %secret:...% expand to the path instead of the contents → the content entries and the runtime entries failed. Reverted, suite green.
  • The runtime entries run the generated assignment through a real bash with the secret directory redirected to a temp dir, so they cover the resolution rather than the string shape.

Review focus

  • formatPackageEnvValue falls back to quoting the raw value when parsing fails, because malformed references are already rejected by validatePackageEnvValues during config parsing. The mutation above is what keeps that assumption honest.
  • A typo in the namespace (%secrets:TOKEN%) stays literal and reaches the package manager as text — the price of expanding only known prefixes. A typo in the id still fails, because the id has to be declared.

Follow-up

Passing a mounted secret to a package manager required shell in the
value, which no longer runs. A `packages[].env` value can now reference
a declared secret with `%secret:<id>%` for its contents or
`%secret_path:<id>%` for its mount path, following the `%image%` family
of werf placeholders; any other `%...%` sequence stays literal.

References are resolved while the package manager runs, so the secret
stays out of the build instructions, the stage digest and the image.
An undeclared or malformed secret id fails during configuration parsing.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
@alexey-igrychev
alexey-igrychev force-pushed the fix/sbom/packages-env-reaches-pm branch from 550011c to 437de24 Compare September 10, 2026 21:45
@alexey-igrychev
alexey-igrychev force-pushed the feat/sbom/packages-env-secret-refs branch from 6ec5de1 to 35c5be4 Compare September 10, 2026 21:45
@alexey-igrychev
alexey-igrychev marked this pull request as ready for review September 11, 2026 10:16
@alexey-igrychev
alexey-igrychev merged commit c06779c into fix/sbom/packages-env-reaches-pm Sep 11, 2026
27 of 29 checks passed
@alexey-igrychev
alexey-igrychev deleted the feat/sbom/packages-env-secret-refs branch September 11, 2026 10:16
alexey-igrychev added a commit that referenced this pull request Sep 11, 2026
* fix(sbom): make packages env variables reach the package manager

For every file-based packages type the install command was generated as
`ENV=value cd "/app" && <tool>`, where the assignment prefix applies only
to `cd`: the package manager itself ran without the variables, so proxy,
index and credential settings declared in `packages[].env` had no effect.
Only `os-pm` worked, because there the prefix directly precedes `pm`.

The prefix now sits between `cd` and the tool, through a single helper
shared by all ecosystems.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>

* feat(sbom): read build secrets in packages env values (#317)

Passing a mounted secret to a package manager required shell in the
value, which no longer runs. A `packages[].env` value can now reference
a declared secret with `%secret:<id>%` for its contents or
`%secret_path:<id>%` for its mount path, following the `%image%` family
of werf placeholders; any other `%...%` sequence stays literal.

References are resolved while the package manager runs, so the secret
stays out of the build instructions, the stage digest and the image.
An undeclared or malformed secret id fails during configuration parsing.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>

---------

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
alexey-igrychev added a commit that referenced this pull request Sep 11, 2026
* fix(sbom): make packages env variables reach the package manager

For every file-based packages type the install command was generated as
`ENV=value cd "/app" && <tool>`, where the assignment prefix applies only
to `cd`: the package manager itself ran without the variables, so proxy,
index and credential settings declared in `packages[].env` had no effect.
Only `os-pm` worked, because there the prefix directly precedes `pm`.

The prefix now sits between `cd` and the tool, through a single helper
shared by all ecosystems.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>

* feat(sbom): read build secrets in packages env values (#317)

Passing a mounted secret to a package manager required shell in the
value, which no longer runs. A `packages[].env` value can now reference
a declared secret with `%secret:<id>%` for its contents or
`%secret_path:<id>%` for its mount path, following the `%image%` family
of werf placeholders; any other `%...%` sequence stays literal.

References are resolved while the package manager runs, so the secret
stays out of the build instructions, the stage digest and the image.
An undeclared or malformed secret id fails during configuration parsing.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>

---------

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants