Problem
gh-aw strict mode (default for Copilot engine workflows) blocks all secrets.* expressions inside the user-defined steps: section, including when they are used in step-level env: bindings. This creates a gap for reusable agent workflows that need to pass tool credentials (API tokens, OAuth keys) to CLI tools via environment variables.
Current Behavior
# csdl-pm.md (agent workflow)
steps:
- name: Export secrets as env vars
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # <-- strict mode ERROR
run: |
echo "SONAR_TOKEN=${SONAR_TOKEN}" >> "$GITHUB_ENV"
Compilation fails with:
error: strict mode: secrets expressions detected in 'steps' section may be leaked
to the agent job. Found: ${{ secrets.SONAR_TOKEN }}, ...
Operations requiring secrets must be moved to a separate job outside the agent job
Why This Is a Problem
Agent workflows that call external APIs (SonarQube, Corona, CIAM, CDETS, Solis, etc.) need tool-specific credentials available as environment variables at runtime. The only way CLI tools can consume these is via process.env / $GITHUB_ENV.
The "move to a separate job" guidance does not work here because:
-
The agent job IS the job — there is no separate non-agent job in a workflow_call agent workflow that can run setup steps with secrets access.
-
The workaround of passing secrets as a string input (secrets_json) is insecure — string inputs and job outputs are not masked by GitHub Actions. This defeats the purpose of strict mode by laundering secrets through a less-protected channel.
-
The secrets: block on workflow_call is the correct, masked transport — but the values can't be consumed because strict mode blocks them in steps.
Workarounds Evaluated
| Approach |
Security |
Drawback |
strict: false in frontmatter |
Secure (secrets flow via secrets: block, masked by GitHub) |
Opts out of all strict mode protections, not just this one |
Pass secrets as secrets_json string input from a caller pre-job |
Insecure — string inputs are not masked in logs |
Defeats the purpose of strict mode entirely |
We are currently using strict: false as the least-bad option, but this is a blunt instrument that disables protections we'd like to keep.
Proposed Solutions
Option A: Allow secrets.* in step-level env: bindings
Step-level env: bindings are a controlled, non-leaky surface. The secret value is bound to a named environment variable for that step only. It's functionally equivalent to what the framework's own generated jobs already do. Strict mode should distinguish between:
env: FOO: ${{ secrets.FOO }} — safe (controlled binding, masked)
run: echo ${{ secrets.FOO }} — unsafe (inline interpolation, potential leak)
Option B: Provide a pre-steps: section
Add a pre-steps: section to the .md frontmatter that runs before the agent job's main steps, with full secrets access. This would allow secret retrieval and $GITHUB_ENV export in a controlled phase that strict mode can audit separately.
pre-steps:
- name: Export tool credentials
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: echo "SONAR_TOKEN=${SONAR_TOKEN}" >> "$GITHUB_ENV"
steps:
- name: Install CLI tools
run: cd .github/workflows/agents/csdl-pm/tools && npm ci
Real-World Use Case
Our csdl-pm agent workflow is a reusable workflow_call agent that orchestrates security scans across 6+ external APIs (SonarQube, Corona, CIAM, CDETS, Solis, Security Insights). It requires 10+ distinct tool credentials:
SONAR_TOKEN, CORONA_TOKEN, CIAM_CLIENT_ID, CIAM_CLIENT_SECRET,
CIAM_TOKEN_URL, CIAM_USER, SOLIS_TOKEN, SI_TOKEN,
CDETS_CONSUMER_KEY, CDETS_CONSUMER_SECRET
Each credential is passed via the secrets: block (properly masked by GitHub) and needs to be exported to $GITHUB_ENV so the agent's bash tool invocations can access them. Setting strict: false is our current workaround, but we lose the other protections strict mode provides.
Environment
gh-aw version: v0.68.0
- Engine:
copilot
- Affected workflow:
csdl-pm.md (reusable workflow_call agent with 10+ tool credentials)
Impact
Any agent workflow that calls external APIs with tool-specific credentials is affected. This pattern is common in:
- Security automation (CSDL scanning, vulnerability management)
- Migration tooling (calling external CI/CD APIs)
- CI/CD orchestration agents (registry pushes, deployment APIs)
- Any agent that integrates with enterprise services behind authentication
Problem
gh-awstrict mode (default for Copilot engine workflows) blocks allsecrets.*expressions inside the user-definedsteps:section, including when they are used in step-levelenv:bindings. This creates a gap for reusable agent workflows that need to pass tool credentials (API tokens, OAuth keys) to CLI tools via environment variables.Current Behavior
Compilation fails with:
Why This Is a Problem
Agent workflows that call external APIs (SonarQube, Corona, CIAM, CDETS, Solis, etc.) need tool-specific credentials available as environment variables at runtime. The only way CLI tools can consume these is via
process.env/$GITHUB_ENV.The "move to a separate job" guidance does not work here because:
The agent job IS the job — there is no separate non-agent job in a
workflow_callagent workflow that can run setup steps with secrets access.The workaround of passing secrets as a string input (
secrets_json) is insecure — string inputs and job outputs are not masked by GitHub Actions. This defeats the purpose of strict mode by laundering secrets through a less-protected channel.The
secrets:block onworkflow_callis the correct, masked transport — but the values can't be consumed because strict mode blocks them in steps.Workarounds Evaluated
strict: falsein frontmattersecrets:block, masked by GitHub)secrets_jsonstring input from a caller pre-jobWe are currently using
strict: falseas the least-bad option, but this is a blunt instrument that disables protections we'd like to keep.Proposed Solutions
Option A: Allow
secrets.*in step-levelenv:bindingsStep-level
env:bindings are a controlled, non-leaky surface. The secret value is bound to a named environment variable for that step only. It's functionally equivalent to what the framework's own generated jobs already do. Strict mode should distinguish between:env: FOO: ${{ secrets.FOO }}— safe (controlled binding, masked)run: echo ${{ secrets.FOO }}— unsafe (inline interpolation, potential leak)Option B: Provide a
pre-steps:sectionAdd a
pre-steps:section to the.mdfrontmatter that runs before the agent job's main steps, with fullsecretsaccess. This would allow secret retrieval and$GITHUB_ENVexport in a controlled phase that strict mode can audit separately.Real-World Use Case
Our
csdl-pmagent workflow is a reusableworkflow_callagent that orchestrates security scans across 6+ external APIs (SonarQube, Corona, CIAM, CDETS, Solis, Security Insights). It requires 10+ distinct tool credentials:Each credential is passed via the
secrets:block (properly masked by GitHub) and needs to be exported to$GITHUB_ENVso the agent's bash tool invocations can access them. Settingstrict: falseis our current workaround, but we lose the other protections strict mode provides.Environment
gh-awversion:v0.68.0copilotcsdl-pm.md(reusableworkflow_callagent with 10+ tool credentials)Impact
Any agent workflow that calls external APIs with tool-specific credentials is affected. This pattern is common in: