diff --git a/.github/aw/create-agentic-workflow.md b/.github/aw/create-agentic-workflow.md index f53401467e7..accda6061ef 100644 --- a/.github/aw/create-agentic-workflow.md +++ b/.github/aw/create-agentic-workflow.md @@ -19,6 +19,7 @@ Create new workflow files under `.github/workflows/` using the installed `gh aw` Load these topic files only when relevant: +- [step-implementation.md](step-implementation.md) for preferred step technologies and shell injection prevention - [campaign.md](campaign.md) for campaign, KPI, pacing, cadence, or `stop-after` - [experiments.md](experiments.md) for experiments, A/B tests, variants, or prompt comparisons - [visual-regression.md](visual-regression.md) for screenshot comparison workflows diff --git a/.github/aw/github-agentic-workflows.md b/.github/aw/github-agentic-workflows.md index 394171c6390..4b4ce44ee30 100644 --- a/.github/aw/github-agentic-workflows.md +++ b/.github/aw/github-agentic-workflows.md @@ -62,6 +62,7 @@ See also: [workflow-constraints.md](workflow-constraints.md) | Editing and recompilation rules | [workflow-editing.md](workflow-editing.md) | | Architectural and security constraints | [workflow-constraints.md](workflow-constraints.md) | | Common design patterns | [workflow-patterns.md](workflow-patterns.md) | +| Step implementation (github-script, shell, Python) | [step-implementation.md](step-implementation.md) | | Frontmatter schema index | [syntax.md](syntax.md) | | Safe outputs index | [safe-outputs.md](safe-outputs.md) | | Trigger patterns | [triggers.md](triggers.md) | diff --git a/.github/aw/reuse.md b/.github/aw/reuse.md index fb3ce5635a8..2329f9b2b4b 100644 --- a/.github/aw/reuse.md +++ b/.github/aw/reuse.md @@ -199,7 +199,7 @@ safe-outputs: contents: read steps: - name: Post to Slack - uses: actions/github-script@v7 + uses: actions/github-script@v9 env: SLACK_TOKEN: "${{ secrets.SLACK_TOKEN }}" CHANNEL: ${{ github.aw.import-inputs.channel }} diff --git a/.github/aw/shared-safe-jobs.md b/.github/aw/shared-safe-jobs.md index 36cf3625def..1f6bcf68e07 100644 --- a/.github/aw/shared-safe-jobs.md +++ b/.github/aw/shared-safe-jobs.md @@ -31,7 +31,7 @@ safe-outputs: required: true steps: - name: Process items - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: script: | const fs = require('fs'); diff --git a/.github/aw/step-implementation.md b/.github/aw/step-implementation.md new file mode 100644 index 00000000000..dc155013a57 --- /dev/null +++ b/.github/aw/step-implementation.md @@ -0,0 +1,125 @@ +--- +description: Guidance for implementing custom steps in agentic workflows, covering preferred technologies and security rules. +--- + +# Step Implementation Guidance + +Use this guidance when writing `steps:`, `pre-steps:`, `post-steps:`, and safe-output job `steps:` in agentic workflows. + +## Priority Order + +Choose the implementation in this order: + +1. **`actions/github-script`** (preferred) +2. **Shell script** (`run:`) +3. **Python** (last resort, data science only) + +--- + +## 1. Prefer `actions/github-script` + +Use `actions/github-script` for steps that call the GitHub API, process structured data, or require reliable JavaScript logic. + +- Use a recent major version such as `v9` — do **not** pin to a full SHA in the workflow markdown. The workflow compiler pins actions to verified SHAs automatically via `actions-lock.json`. +- JavaScript runs in Node.js: standard library (`fs`, `path`, `child_process`), the pre-authenticated `github` Octokit client, and `core`/`exec` helpers from `@actions/core` and `@actions/exec` are available. +- Pass untrusted values through `process.env` (set with `env:`) instead of interpolating `${{ ... }}` expressions directly into the `script:` block. + +```yaml +steps: + - name: Fetch open issues + uses: actions/github-script@v9 + env: + LABEL: ${{ github.event.label.name }} + with: + script: | + const label = process.env.LABEL; + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: label, + state: 'open', + }); + const fs = require('fs'); + fs.writeFileSync('/tmp/gh-aw/agent/issues.json', JSON.stringify(issues)); +``` + +--- + +## 2. Shell Script (`run:`) + +Use shell when the step is a straightforward sequence of CLI commands (`gh`, `jq`, `curl`, standard Unix utilities) and JavaScript would add unnecessary complexity. + +### Shell injection rules + +Shell injection occurs when untrusted content from GitHub context expressions is interpolated into a `run:` command. Follow these rules: + +- **Never interpolate `${{ ... }}` expressions directly into `run:` scripts.** Instead, assign them to environment variables with `env:` and reference `$VAR_NAME` in the script. +- Treat all `github.event.*` values, user-supplied inputs, issue/PR titles, and comment bodies as untrusted. + +**Unsafe — do not do this:** + +```yaml +steps: + - name: Comment + run: gh issue comment ${{ github.event.issue.number }} --body "${{ github.event.issue.title }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +**Safe — pass untrusted values through environment variables:** + +```yaml +steps: + - name: Comment + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + ISSUE_TITLE: ${{ github.event.issue.title }} + run: gh issue comment "$ISSUE_NUMBER" --body "$ISSUE_TITLE" +``` + +Additional shell rules: + +- Quote all variable expansions: `"$VAR"`, not `$VAR`. +- Use `--` or named flags to separate options from arguments when passing values to CLI tools. +- Set `GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}` on every step that calls `gh`. +- Write prepared data to `/tmp/gh-aw/agent/` and trim large outputs before handing them to the agent. +- Use `jq` to reduce JSON payload size. + +--- + +## 3. Python (last resort, data science only) + +Use Python only when the task genuinely requires scientific computing, statistical analysis, or data-science libraries (`pandas`, `numpy`, `matplotlib`, etc.) that have no shell or JavaScript equivalent. + +- Prefer the system Python or a pinned runtime via `runtimes:` rather than installing Python in the step itself. +- Install dependencies via `pip install` only when necessary; pin versions. +- Apply the same injection rules as shell: pass `${{ ... }}` values through environment variables (`os.environ`), never via f-strings or string concatenation into subcommands. + +```yaml +steps: + - name: Analyse metrics + env: + DATA_PATH: /tmp/gh-aw/agent/metrics.csv + run: | + python3 - <<'EOF' + import os, json, csv, statistics + path = os.environ["DATA_PATH"] + with open(path) as f: + rows = list(csv.DictReader(f)) + values = [float(r["latency_ms"]) for r in rows] + result = {"p50": statistics.median(values), "mean": statistics.mean(values)} + with open("/tmp/gh-aw/agent/summary.json", "w") as out: + json.dump(result, out) + EOF +``` + +--- + +## Summary Table + +| Need | Use | +|---|---| +| GitHub API calls, structured JSON processing | `actions/github-script@v9` | +| CLI commands (`gh`, `jq`, `curl`, Unix tools) | Shell `run:` | +| Scientific computing, data-science libraries | Python (last resort) | diff --git a/.github/aw/workflow-patterns.md b/.github/aw/workflow-patterns.md index a7aa26de8d6..cd48f3ef481 100644 --- a/.github/aw/workflow-patterns.md +++ b/.github/aw/workflow-patterns.md @@ -105,6 +105,8 @@ Rules: - add `permissions: actions: read` for downloading workflow logs/artifacts - use `jq` to reduce JSON payload size +For implementation technology (when to use `actions/github-script`, shell, or Python) and shell injection prevention, see [step-implementation.md](step-implementation.md). + ## PR Visual Regression Pattern For PR UI validation and screenshot diffs: