Repository navigation
Support passing arbitrary secrets as environment variables to test jobs #52
Description
Context
Module test jobs need a safe way to receive caller-selected secrets and non-secret configuration without hard-coding every possible test credential name in Process-PSModule. Some modules need no test data, while others need module-specific environment variable names for API tokens, app credentials, service URLs, usernames, space keys, or similar test configuration.
Technical Decisions
Interface: Add one optional workflow-call secret named TestData to .github/workflows/workflow.yml. TestData is a single-line JSON object with optional secrets and variables maps:
{ "secrets": { "NAME": "value" }, "variables": { "NAME": "value" } }Exposure model: Entries from both maps are exposed as environment variables in BeforeAll-ModuleLocal, Test-ModuleLocal, and AfterAll-ModuleLocal. Values under secrets are masked before they are written to GITHUB_ENV; values under variables are not masked.
Security boundary: Do not use broad secrets: inherit. The calling workflow must explicitly list the values it wants to expose in TestData, so the shared workflow only receives the caller-selected test data.
Validation: TestData must parse as a JSON object containing only optional secrets and variables maps. Map values must be scalar values. Keys must be safe environment-variable names matching ^[A-Za-z_][A-Za-z0-9_]*$ and must not override reserved variables such as PATH, CI, GITHUB_*, RUNNER_* or ACTIONS_*.
Masking and log readability: TestData must be passed as a single-line value after YAML folding. Pretty-printed JSON with nested indentation is not safe because YAML preserves more-indented lines inside a folded scalar; brace-only lines can become individual GitHub masks and pollute unrelated logs with ***.
Environment-scoped values: GitHub Environment secrets and variables are usable when the calling job sets environment: and explicitly includes those values in TestData; they are not exposed automatically.
Acceptance Criteria
- Test jobs can read arbitrary, module-defined secret names without changing the shared workflow.
- No module-specific secret names are hard-coded in
.github/workflows/workflow.ymlor the*-ModuleLocalworkflows. - Secret values are masked in logs before being exposed through
GITHUB_ENV. - Non-secret configuration is supported through the
variablesmap. - Modules that need no test data work unchanged by omitting
TestData. - Environment-scoped secrets and variables are documented as requiring
environment:on the calling job plus explicit inclusion inTestData. - The duplicated fixed
TEST_*pass-through blocks are removed from the main reusable workflow and*-ModuleLocalworkflows.
Implementation Plan
- Replace the fixed workflow-call
TEST_*secret inputs on.github/workflows/workflow.ymlwithTestData. - Pass
TestDatatoBeforeAll-ModuleLocal,Test-ModuleLocal, andAfterAll-ModuleLocal. - Parse
TestDatain the*-ModuleLocalworkflows and expose selected values throughGITHUB_ENV. - Mask secret values and avoid printing secret key names.
- Validate JSON shape, environment-variable names, reserved names, and scalar values before writing to
GITHUB_ENV. - Document the breaking migration path from fixed
TEST_*inputs toTestData. - Document safe YAML folding, variable syntax, base64 guidance for complex secrets, and environment-scoped values.
Related
- Implemented by PR 🌟 [Major]: Fixed test secret inputs replaced by TestData #365.
- Also addresses the duplicated fixed-secret pass-through concern in Replace duplicated test-secret blocks with
secrets: inheritin ModuleLocal workflows #322 without adopting broadsecrets: inherit.
Activity
[-]🚀[Feature]: Make it possible to send env's into the workflow[/-][+]🚀[Feature]: Securely Passing Arbitrary Secrets to Process-PSModule reusable workflow from calling workflow[/+]MariusStorhaug commentedon Apr 18, 2025
Specification: Securely Passing Arbitrary Secrets to Reusable GitHub Workflows
Summary
Implement a secure method for passing an unlimited number of arbitrary secrets to GitHub reusable workflows using asymmetric encryption (libsodium), allowing secrets to be managed without explicit declaration within the reusable workflow.
Background
GitHub reusable workflows currently require all secrets to be explicitly declared, limiting flexibility when dynamically managing many secrets. To overcome this, encrypted variables (ciphertexts) will be passed through environment variables (
env) and decrypted at runtime with a private key stored securely in GitHub secrets.Proposed Solution
Workflow
Preparation
PRIVATE_KEY_SECRET).Workflow Structure
Caller Workflow:
Reusable Workflow:
Security Considerations
::add-mask::).Benefits
Acceptance Criteria
MariusStorhaug commentedon Apr 18, 2025
Did this to support this with less input.
PSModule/Sodium#30
With this we only need to store the private key and encrypted secrets in variables.
MariusStorhaug commentedon Apr 18, 2025
This is basically BYOK for GitHub secrets?
MariusStorhaug commentedon Apr 18, 2025
Issue: To make new keys, the user needs to have the private key.
Alternatives:
MariusStorhaug commentedon Apr 18, 2025
Need functionality in Process-PSModule for how to do this "seamlessly". Would work great if we had the BYOK support from the
GitHubmodule.Just add a switch for PrivateKey? Lets explore...
MariusStorhaug commentedon Jan 17, 2026
Could potentially just store it in KeyVault?
[-]🚀[Feature]: Securely Passing Arbitrary Secrets to Process-PSModule reusable workflow from calling workflow[/-][+]Support passing arbitrary secrets as environment variables to test jobs[/+]MariusStorhaug commentedon Apr 5, 2026
Restructured issue description into the standard three-section format.
Minorlabel (new feature)MariusStorhaug commentedon May 3, 2026
Cross-reference: The structural duplication of test secrets across
BeforeAll-ModuleLocal.yml,Test-ModuleLocal.yml, andAfterAll-ModuleLocal.ymlis tracked in #322 (Replace duplicated test-secret blocks withsecrets: inherit). That issue addresses the DRY violation with the current fixed set of secrets, while this issue covers the broader capability of passing arbitrary secrets.MariusStorhaug commentedon Jul 4, 2026
Empirical finding: the proposed
env:input can't carry secrets — here's what doesI ran an isolated experiment covering every way a caller can expose secrets to a reusable workflow — provider + two independent consumers, full write-up with run links:
The
env:map inwith:(as proposed) is rejected at validationA reusable-workflow-call
with:block cannot reference thesecretscontext, sowith: { env: "TEST_PAT: ${{ secrets.TEST_PAT }}" }never even reaches the parsing step. Proven three independent ways:Unrecognized named-value: 'secrets' ... within expression: secrets.REPO_SECRETactionlint:context "secrets" is not allowed here. available contexts are "github","inputs","matrix","needs","strategy","vars"What works — and needs no new input
Consumers already call us with
secrets: inherit, so the entire secrets context is available inside the workflow. A workflow step'senv:can referencetoJSON(secrets)(awith:can't). Expand it (plusvars) into environment variables on the test job, and existing Pester tests keep reading$env:TEST_*unchanged:Proven across two unrelated consumers using the same runner with zero secret names in it:
REPO_SECRETCONSUMER_A_REGION,CONSUMER_A_BASE_URLDOMENESHOP_TOKEN,DOMENESHOP_API_SECRETDOMENESHOP_BASE_URL,DOMENESHOP_TENANTNeither saw the other's names; the runner was unchanged between them. Modules that need no secrets are a no-op.
Notes / caveats (all verified)
::add-mask::above also covers values expanded into env vars.secrets: inheritcarries repo + org secrets only. If a module's test creds live in a GitHub Environment, the test job must also setenvironment: <name>(inherit alone won't expose environment secrets).secrets: inheritin ModuleLocal workflows #322: drop the ~7 duplicated explicitTEST_*passes and keepsecrets: inheritend-to-end.with:/env:can reference secrets — but thetoJSONstep above needs no per-module wiring.I've revised the issue body to this approach and kept the original proposal in a collapsed section for history.
MariusStorhaug commentedon Jul 8, 2026
Updated the issue body to match the final PR #365 implementation: the solution is the explicit
TestDataworkflow-call secret withsecretsandvariablesmaps, not broadsecrets: inherit. The issue now records the validation rules, masking behavior, environment-scoped value guidance, and checked acceptance criteria that the PR implements.