diff --git a/.github/workflows/gh-aw-dependency-review.lock.yml b/.github/workflows/gh-aw-dependency-review.lock.yml
new file mode 100644
index 00000000..ef80f597
--- /dev/null
+++ b/.github/workflows/gh-aw-dependency-review.lock.yml
@@ -0,0 +1,1526 @@
+#
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw. DO NOT EDIT.
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# Not all edits will cause changes to this file.
+#
+# For more information: https://github.github.com/gh-aw/introduction/overview/
+#
+# Analyze Dependabot and Renovate dependency update PRs
+#
+# Resolved workflow manifest:
+# Imports:
+# - gh-aw-fragments/elastic-tools.md
+# - gh-aw-fragments/formatting.md
+# - gh-aw-fragments/mcp-pagination.md
+# - gh-aw-fragments/messages-footer.md
+# - gh-aw-fragments/network-ecosystems.md
+# - gh-aw-fragments/rigor.md
+# - gh-aw-fragments/runtime-setup.md
+# - gh-aw-fragments/safe-output-add-comment-pr.md
+#
+# inlined-imports: true
+#
+# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"3ba7541be6ce532d14ab936405f1b65aca7c9b6a99ab3c8c299c0dfe51e16ea2"}
+
+name: "Dependency Review"
+"on":
+ # bots: # Bots processed as bot check in pre-activation job
+ # - ${{ inputs.allowed-bot-users }} # Bots processed as bot check in pre-activation job
+ # - dependabot[bot] # Bots processed as bot check in pre-activation job
+ # - renovate[bot] # Bots processed as bot check in pre-activation job
+ # roles: # Roles processed as role check in pre-activation job
+ # - admin # Roles processed as role check in pre-activation job
+ # - maintainer # Roles processed as role check in pre-activation job
+ # - write # Roles processed as role check in pre-activation job
+ workflow_call:
+ inputs:
+ additional-instructions:
+ default: ""
+ description: Repo-specific instructions appended to the agent prompt
+ required: false
+ type: string
+ allowed-bot-users:
+ default: github-actions[bot]
+ description: Allowlisted bot actor usernames (comma-separated)
+ required: false
+ type: string
+ messages-footer:
+ default: ""
+ description: Footer appended to all agent comments and reviews
+ required: false
+ type: string
+ model:
+ default: gpt-5.3-codex
+ description: AI model to use
+ required: false
+ type: string
+ setup-commands:
+ default: ""
+ description: Shell commands to run before the agent starts (dependency install, build, etc.)
+ required: false
+ type: string
+ secrets:
+ COPILOT_GITHUB_TOKEN:
+ required: true
+
+permissions: {}
+
+concurrency:
+ cancel-in-progress: true
+ group: dependency-review-${{ github.event.pull_request.number }}
+
+run-name: "Dependency Review"
+
+jobs:
+ activation:
+ needs: pre_activation
+ if: needs.pre_activation.outputs.activated == 'true'
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Validate context variables
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs');
+ await main();
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ .github
+ .agents
+ fetch-depth: 1
+ persist-credentials: false
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "gh-aw-dependency-review.lock.yml"
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ run: |
+ bash /opt/gh-aw/actions/create_prompt_first.sh
+ {
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat "/opt/gh-aw/prompts/xpia.md"
+ cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
+ cat "/opt/gh-aw/prompts/markdown.md"
+ cat "/opt/gh-aw/prompts/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_EOF'
+
+ Tools: add_comment, add_labels, missing_tool, missing_data
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ ## MCP Servers
+
+ - **`generate_agents_md`** — generates a summary of the repository's structure, conventions, and coding guidelines from AGENTS.md and related files. Call at the start of every workflow to get repo context.
+ - **`search_code`** — grep-style search across public GitHub repositories. Use for finding usage patterns in upstream libraries, reference implementations, or examples in open-source projects. This searches *public GitHub repos*, not the local codebase — if available you can use `grep` and file reading for local code.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ ## Formatting Guidelines
+
+ - Lead with the most important information — your first sentence should be the key takeaway
+ - Be concise and actionable — no filler or praise
+ - Use `` and `` tags for long sections to keep responses scannable
+ - Wrap branch names and @-references in backticks to avoid pinging users
+ - Include code snippets with file paths and line numbers when referencing the codebase
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ ## Rigor
+
+ **Silence is better than noise. A false positive wastes a human's time and erodes trust in every future report.**
+
+ - If you claim something is missing or broken, show the exact evidence in the code — file path, line number, and what you observed.
+ - If a conclusion depends on assumptions you haven't confirmed, do not assert it. Verify first; if you cannot verify, do not report.
+ - "I don't know" is better than a wrong answer. `noop` is better than a speculative finding.
+ - It's worth the time to verify now versus guessing and forcing someone else to verify later.
+ - Before submitting any output, re-read it as a skeptical reviewer. Ask: "Would a senior engineer on this team find this useful, or would they close it immediately?" If the answer is "close," call `noop` instead.
+ - Only report findings you would confidently defend in a code review. If you feel the need to hedge with "might," "could," or "possibly," the finding is not ready to file.
+ - Be thorough, spend the time to investigate and verify. There is no rush. Do your best work.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ ## MCP Pagination
+
+ MCP tool responses have a **25,000 token limit**. When responses exceed this limit, the call fails and you must retry with pagination — wasting turns and tokens. Use proactive pagination to stay under the limit.
+
+ ### Recommended `perPage` Values
+
+ - **5-10**: For detailed items (PR diffs, files with patches, issues with comments)
+ - **20-30**: For medium-detail lists (commits, review comments, issue lists)
+ - **50-100**: For simple list operations (branches, labels, tags)
+
+ ### Pagination Pattern
+
+ When you need all results from a paginated API:
+
+ 1. Fetch the first page with a conservative `perPage` value
+ 2. Process the results before fetching the next page
+ 3. Continue fetching pages until you receive fewer results than `perPage` (indicating the last page)
+
+ ### Error Recovery
+
+ If you see an error like:
+ - `MCP tool response exceeds maximum allowed tokens (25000)`
+ - `Response too large for tool [tool_name]`
+
+ Retry the same call with a smaller `perPage` value (halve it).
+
+ ### Tips
+
+ - **Start small**: It's better to make multiple small requests than one that fails
+ - **Fetch incrementally**: Get an overview first, then details for specific items
+ - **Use filters**: Combine `perPage` with state, label, or date filters to reduce result size
+ - **Process as you go**: Don't accumulate all pages before acting — process each batch immediately
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ ## Message Footer
+
+ A footer is automatically appended to all comments and reviews. Do not add your own footer or sign-off — the runtime handles this.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ ## add-comment Limitations
+
+ - **Body**: Max 65,536 characters (including any footer added by gh-aw). Keep well under this limit.
+ - **Mentions**: Max 10 `@` mentions per comment.
+ - **Links**: Max 50 URLs per comment.
+ - **HTML**: Only safe tags allowed (`details`, `summary`, `code`, `pre`, `blockquote`, `table`, `b`, `em`, `strong`, `h1`–`h6`, `hr`, `br`, `li`, `ol`, `ul`, `p`, `sub`, `sup`). Other tags are converted to parentheses.
+ - **URLs**: Only HTTPS URLs to allowed domains. Non-HTTPS and non-allowed domains are redacted.
+ - **Bot triggers**: References like `fixes #123` or `closes #456` are neutralized to prevent unintended issue closures.
+
+ If you exceed 10 mentions or 50 links, the comment will be rejected.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ # Dependency Review Agent
+
+ Analyze dependency update pull requests (Dependabot, Renovate) in __GH_AW_GITHUB_REPOSITORY__. Provide a detailed analysis comment covering changelog highlights, compatibility, risk, and ecosystem-specific checks.
+
+ ## Context
+
+ - **Repository**: __GH_AW_GITHUB_REPOSITORY__
+ - **PR**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ — __GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE__
+ - **PR Author**: __GH_AW_GITHUB_ACTOR__
+
+ ## Constraints
+
+ This workflow is read-only. You can read files, search code, run commands, and comment on PRs — but your only outputs are an analysis comment and optional labels.
+
+ ## Instructions
+
+ ### Step 1: Gather Context
+
+ 1. Call `generate_agents_md` to get the repository's coding guidelines and conventions. If this fails, continue without it.
+ 2. Call `pull_request_read` with method `get` on PR #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ to get full PR details (author, description, branches).
+ 3. Call `pull_request_read` with method `get_diff` to see exactly what changed.
+ 4. Call `pull_request_read` with method `get_files` to get the list of changed files.
+
+ ### Step 2: Identify and Classify Updated Dependencies
+
+ Parse the diff to identify each dependency being updated. For each dependency, extract:
+ - **Ecosystem**: GitHub Actions, Buildkite plugin, Go module, npm package, Python (pip/Poetry/uv), Maven/Gradle (Java), or other
+ - **Package name**: e.g. `actions/checkout`, `golang.org/x/net`, `express`, `requests`
+ - **Old version**: tag, SHA, or version before the update
+ - **New version**: tag, SHA, or version after the update
+
+ Classify each dependency by looking at the files changed:
+ - `.github/workflows/*.yml` or `.github/workflows/*.yaml` → **GitHub Actions**
+ - `pipeline.yml`, `.buildkite/` files → **Buildkite plugin**
+ - `go.mod`, `go.sum` → **Go module**
+ - `package.json`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` → **npm/Node**
+ - `pyproject.toml`, `requirements*.txt`, `Pipfile*`, `poetry.lock`, `uv.lock` → **Python**
+ - `pom.xml`, `build.gradle`, `build.gradle.kts`, `gradle.lockfile` → **Java/Kotlin (Maven/Gradle)**
+ - Other manifest files → classify by ecosystem
+
+ ### Step 3: Analyze Each Dependency
+
+ For each updated dependency, perform the following checks:
+
+ #### 3a: Commit Verification (GitHub Actions only)
+
+ If the action reference uses a commit SHA (e.g. `uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd`):
+
+ 1. Verify the commit is a verified commit by checking the GitHub API:
+ ```bash
+ gh api repos/{owner}/{repo}/commits/{sha} --jq '.commit.verification.verified'
+ ```
+ 2. If the commit is **not verified**, flag this prominently. Unverified commits in pinned actions are a supply-chain risk.
+ 3. Check whether the commit SHA corresponds to a known release tag:
+ ```bash
+ gh api repos/{owner}/{repo}/git/matching-refs/tags --jq '.[].ref' | head -20
+ ```
+ Then verify the tag points to the expected SHA.
+
+ #### 3b: Changelog and Release Notes
+
+ For dependencies hosted on GitHub, fetch the release notes:
+ 1. Fetch the release notes for the new version from the dependency's repository:
+ ```bash
+ gh api repos/{owner}/{repo}/releases/tags/{new_tag} --jq '.body' 2>/dev/null
+ ```
+ 2. If no release exists for the exact tag, check the latest releases:
+ ```bash
+ gh api repos/{owner}/{repo}/releases --jq '.[].tag_name' | head -10
+ ```
+ 3. For non-GitHub dependencies, check the package registry or changelog files in the source repo when available.
+ 4. Summarize key changes between the old and new versions, focusing on:
+ - Breaking changes or removed features
+ - New required configuration or changed defaults
+ - Security fixes
+ - Deprecations
+ - Notable new features relevant to how this repo uses the dependency
+
+ #### 3c: Usage Analysis
+
+ 1. Search the repository for all places the dependency is used. The search method depends on the ecosystem:
+ - **GitHub Actions**: `grep -rn '{owner}/{repo}' .github/workflows/ --include='*.yml' --include='*.yaml'`
+ - **Go**: `grep -rn '{module}' --include='*.go'` (look for import statements)
+ - **npm/Node**: `grep -rn "require('{package}')\|from '{package}'" --include='*.js' --include='*.ts' --include='*.mjs' --include='*.cjs'`
+ - **Python**: `grep -rn "import {package}\|from {package}" --include='*.py'`
+ - **Java**: `grep -rn '{groupId}' --include='*.java' --include='*.kt' --include='*.gradle' --include='*.xml'`
+ 2. For each usage, note:
+ - Which files and modules use it
+ - What APIs, functions, or features are consumed
+ - For GitHub Actions: what inputs are passed and outputs consumed
+ - For GitHub Actions: what events trigger the workflow
+
+ 3. Cross-reference the usage against the changelog:
+ - Are any APIs, inputs, or features used by this repo deprecated or removed in the new version?
+ - Are there breaking changes to consumed interfaces?
+ - Are there new required configuration options that are not provided?
+
+ #### 3d: Testability Assessment
+
+ 1. Check the trigger events for each workflow that uses the updated dependency.
+ 2. If a workflow is **only** triggered by `push` (to main/default branch), `release`, `schedule`, or `workflow_dispatch`, it **cannot be validated by the PR itself**. Flag this as higher risk.
+ 3. If a workflow is triggered by `pull_request` or `pull_request_target`, it can be exercised in the PR context.
+
+ #### 3e: Pin Format Check (Buildkite plugins)
+
+ For Buildkite plugin updates:
+ 1. Check if the update moves from a SHA-pinned version to a mutable tag (higher risk).
+ 2. Check if the update moves from one mutable tag to another mutable tag (moderate risk).
+ 3. SHA-to-SHA or tag-to-SHA-pinned updates are preferred.
+
+ #### 3f: Ecosystem-Specific Guidance
+
+ Apply the following additional checks based on the dependency ecosystem:
+
+ **Go modules:**
+ - Check if this is a major version bump (e.g. v1 → v2) — Go major versions change the import path, which is a breaking change requiring code updates across the repo.
+ - For indirect dependency updates, note that these are transitive and generally lower risk.
+ - Check for `// Deprecated:` annotations in the module if accessible.
+
+ **npm / Node packages:**
+ - Check if this is a major semver bump — major versions typically signal breaking changes.
+ - Look for peer dependency conflicts that may arise from the update.
+ - For `devDependencies`, note that these only affect development and CI, not production.
+
+ **Python packages (pip, Poetry, uv):**
+ - Check if this is a major version bump — may indicate breaking API changes.
+ - Check for minimum Python version requirements that may have changed.
+ - For packages with native extensions (e.g. `numpy`, `cryptography`), note potential build or platform compatibility changes.
+
+ **Java / Kotlin (Maven, Gradle):**
+ - Check if this is a major version bump — may indicate breaking API changes.
+ - Note if the groupId or artifactId changed (dependency relocation).
+ - For Spring or framework dependencies, check for minimum JDK version changes.
+
+ ### Step 4: Determine Labels
+
+ Based on the analysis, determine if labels should be applied:
+
+ - **`needs-human-review`**: Apply when ANY of these conditions are met:
+ - A dependency update introduces breaking changes that affect this repo's usage
+ - A GitHub Actions commit SHA is not verified
+ - A Buildkite plugin moves from SHA-pinned to mutable tag, or between mutable tags
+ - The changelog indicates breaking changes
+ - A major version bump in any ecosystem (e.g. v1 → v2 in Go, major semver in npm/Python/Java)
+
+ - **`higher-risk`**: Apply when:
+ - The updated dependency is used only in workflows triggered by push-to-main, release, schedule, or workflow_dispatch (cannot be validated in PR context)
+
+ Only apply `needs-human-review` and `higher-risk` labels.
+
+ ### Step 5: Post Analysis Comment
+
+ Call `add_comment` on the PR with a structured analysis. Use the following format:
+
+ > ## Dependency Update Analysis
+ >
+ > **Summary**: [One-line summary of the update and overall risk assessment]
+ >
+ > ### [Dependency 1: package vOLD → vNEW]
+ >
+ > **Ecosystem**: [GitHub Actions / Go / npm / Python / Java / Buildkite / other]
+ >
+ > | Check | Result |
+ > | --- | --- |
+ > | Breaking changes | ✅ None found / ⚠️ Found (details below) |
+ > | Testable in PR | ✅ Yes / ⚠️ No — workflow only runs on [events] |
+ > | Commit verified | ✅ Yes / ⚠️ No *(GitHub Actions only)* |
+ > | Pin format | ✅ SHA-pinned / ⚠️ Mutable tag *(GitHub Actions / Buildkite only)* |
+ >
+ > Only include rows relevant to the dependency ecosystem. For example, "Commit verified" and "Pin format" only apply to GitHub Actions and Buildkite.
+ >
+ >
+ > Changelog highlights (vOLD → vNEW)
+ >
+ > [Key changes from release notes]
+ >
+ >
+ >
+ > Usage in this repository
+ >
+ > [List of files/modules using this dependency and relevant APIs/inputs/outputs]
+ >
+ >
+ >
+ > Compatibility assessment
+ >
+ > [Analysis of whether current usage is compatible with the new version, including ecosystem-specific notes]
+ >
+ >
+ > ### Labels Applied
+ > [List of labels applied and why, or "No labels applied"]
+
+ If the analysis found no issues, keep the comment concise — do not pad with unnecessary detail.
+
+ ### Step 6: Apply Labels
+
+ If any labels were determined in Step 4, call `add_labels` to apply them to the PR.
+
+ __GH_AW_EXPR_49B959F1__
+
+ GH_AW_PROMPT_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_EXPR_49B959F1: process.env.GH_AW_EXPR_49B959F1,
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED
+ }
+ });
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ - name: Upload prompt artifact
+ if: success()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: prompt
+ path: /tmp/gh-aw/aw-prompts/prompt.txt
+ retention-days: 1
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ issues: read
+ pull-requests: read
+ concurrency:
+ group: "gh-aw-copilot-dependency-review-${{ github.event.pull_request.number }}"
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_WORKFLOW_ID_SANITIZED: ghawdependencyreview
+ outputs:
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - if: hashFiles('go.mod') != ''
+ name: Setup Go
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
+ with:
+ cache: true
+ go-version-file: go.mod
+ - if: hashFiles('.python-version') != ''
+ name: Setup Python
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
+ with:
+ python-version-file: .python-version
+ - if: hashFiles('.node-version') != ''
+ name: Setup Node.js (.node-version)
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
+ with:
+ node-version-file: .node-version
+ - if: hashFiles('.node-version') == '' && hashFiles('.nvmrc') != ''
+ name: Setup Node.js (.nvmrc)
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
+ with:
+ node-version-file: .nvmrc
+ - if: hashFiles('.ruby-version') != ''
+ name: Setup Ruby
+ uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
+ with:
+ bundler-cache: true
+ ruby-version: .ruby-version
+ - id: setup-uv
+ if: hashFiles('pyproject.toml', 'uv.lock') != ''
+ name: Setup uv
+ uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
+ - env:
+ UV_PATH: ${{ steps.setup-uv.outputs.uv-path }}
+ WORKSPACE: ${{ github.workspace }}
+ if: hashFiles('pyproject.toml', 'uv.lock') != ''
+ name: Expose uv in workspace
+ run: |-
+ set -euo pipefail
+ install_dir="$WORKSPACE/.gh-aw-tools/bin"
+ mkdir -p "$install_dir"
+ cp "$UV_PATH" "$install_dir/uv"
+ chmod +x "$install_dir/uv"
+ echo "$install_dir" >> "$GITHUB_PATH"
+ shell: bash
+ - env:
+ SETUP_COMMANDS: ${{ inputs.setup-commands }}
+ if: ${{ inputs.setup-commands != '' }}
+ name: Repo-specific setup
+ run: eval "$SETUP_COMMANDS"
+
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ github.event.pull_request
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Generate agentic run info
+ id: generate_aw_info
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const fs = require('fs');
+
+ const awInfo = {
+ engine_id: "copilot",
+ engine_name: "GitHub Copilot CLI",
+ model: "${{ inputs.model }}",
+ version: "",
+ agent_version: "0.0.417",
+ workflow_name: "Dependency Review",
+ experimental: false,
+ supports_tools_allowlist: true,
+ run_id: context.runId,
+ run_number: context.runNumber,
+ run_attempt: process.env.GITHUB_RUN_ATTEMPT,
+ repository: context.repo.owner + '/' + context.repo.repo,
+ ref: context.ref,
+ sha: context.sha,
+ actor: context.actor,
+ event_name: context.eventName,
+ staged: false,
+ allowed_domains: ["agents-md-generator.fastmcp.app","artifacts.elastic.co","clojure","cloud.elastic.co","containers","dart","defaults","dotnet","ela.st","elastic.co","elastic.dev","elixir","fonts","github","github-actions","go","haskell","java","kotlin","linux-distros","node","node-cdns","perl","php","playwright","public-code-search.fastmcp.app","python","ruby","rust","scala","swift","terraform","www.elastic.co","zig"],
+ firewall_enabled: true,
+ awf_version: "v0.23.0",
+ awmg_version: "v0.1.5",
+ steps: {
+ firewall: "squid"
+ },
+ created_at: new Date().toISOString()
+ };
+
+ // Write to /tmp/gh-aw directory to avoid inclusion in PR
+ const tmpPath = '/tmp/gh-aw/aw_info.json';
+ fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
+ console.log('Generated aw_info.json at:', tmpPath);
+ console.log(JSON.stringify(awInfo, null, 2));
+
+ // Set model as output for reuse in other steps/jobs
+ core.setOutput('model', awInfo.model);
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.417
+ - name: Install awf binary
+ run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0
+ - name: Determine automatic lockdown mode for GitHub MCP Server
+ id: determine-automatic-lockdown
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ with:
+ script: |
+ const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p /opt/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
+ {"add_comment":{"max":1},"add_labels":{"allowed":["needs-human-review","higher-risk"],"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_EOF
+ cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF'
+ [
+ {
+ "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 1 comment(s) can be added.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "body": {
+ "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.",
+ "type": "string"
+ },
+ "item_number": {
+ "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.",
+ "type": "number"
+ }
+ },
+ "required": [
+ "body"
+ ],
+ "type": "object"
+ },
+ "name": "add_comment"
+ },
+ {
+ "description": "Add labels to an existing GitHub issue or pull request for categorization and filtering. Labels must already exist in the repository. For creating new issues with labels, use create_issue with the labels property instead. CONSTRAINTS: Maximum 3 label(s) can be added. Only these labels are allowed: [needs-human-review higher-risk].",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "item_number": {
+ "description": "Issue or PR number to add labels to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/issues/456). If omitted, adds labels to the issue or PR that triggered this workflow. Only works for issue or pull_request event triggers. For schedule, workflow_dispatch, or other triggers, item_number is required — omitting it will silently skip the label operation.",
+ "type": "number"
+ },
+ "labels": {
+ "description": "Label names to add (e.g., ['bug', 'priority-high']). Labels must exist in the repository.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "name": "add_labels"
+ },
+ {
+ "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
+ "type": "string"
+ },
+ "tool": {
+ "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "reason"
+ ],
+ "type": "object"
+ },
+ "name": "missing_tool"
+ },
+ {
+ "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "message": {
+ "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "type": "object"
+ },
+ "name": "noop"
+ },
+ {
+ "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "context": {
+ "description": "Additional context about the missing data or where it should come from (max 256 characters).",
+ "type": "string"
+ },
+ "data_type": {
+ "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
+ "type": "string"
+ }
+ },
+ "required": [],
+ "type": "object"
+ },
+ "name": "missing_data"
+ }
+ ]
+ GH_AW_SAFE_OUTPUTS_TOOLS_EOF
+ cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
+ {
+ "add_comment": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "item_number": {
+ "issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ }
+ }
+ },
+ "add_labels": {
+ "defaultMax": 5,
+ "fields": {
+ "item_number": {
+ "issueOrPRNumber": true
+ },
+ "labels": {
+ "required": true,
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 128
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_data": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "context": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "data_type": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "reason": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ }
+ }
+ GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ # Mask immediately to prevent timing vulnerabilities
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${API_KEY}"
+
+ PORT=3001
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ DEBUG: '*'
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export DEBUG
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash /opt/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP Gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export MCP_GATEWAY_API_KEY
+ export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
+ mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export DEBUG="*"
+
+ export GH_AW_ENGINE="copilot"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5'
+
+ mkdir -p /home/runner/.copilot
+ cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "agents-md-generator": {
+ "type": "http",
+ "url": "https://agents-md-generator.fastmcp.app/mcp",
+ "tools": [
+ "generate_agents_md"
+ ]
+ },
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.31.0",
+ "env": {
+ "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "repos,issues,pull_requests,search,actions"
+ }
+ },
+ "public-code-search": {
+ "type": "http",
+ "url": "https://public-code-search.fastmcp.app/mcp",
+ "tools": [
+ "search_code"
+ ]
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
+ }
+ }
+ GH_AW_MCP_CONFIG_EOF
+ - name: Generate workflow overview
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
+ await generateWorkflowOverview(core);
+ - name: Download prompt artifact
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: prompt
+ path: /tmp/gh-aw/aw-prompts
+ - name: Clean git credentials
+ run: bash /opt/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 60
+ run: |
+ set -o pipefail
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "*.docker.com,*.docker.io,*.githubusercontent.com,*.hackage.haskell.org,*.jsr.io,*.pythonhosted.org,*.rvm.io,*.vsblob.vsassets.io,adoptium.net,agents-md-generator.fastmcp.app,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.rubygems.org,api.snapcraft.io,apt.llvm.org,apt.releases.hashicorp.com,archive.apache.org,archive.ubuntu.com,archlinux.org,artifacts.elastic.co,auth.docker.io,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bitbucket.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,builds.hex.pm,bun.sh,bundler.rubygems.org,cache.ruby-lang.org,cdn.azul.com,cdn.cocoapods.org,cdn.hex.pm,cdn.jsdelivr.net,cdn.playwright.dev,cdn.redhat.com,cdn.sheetjs.com,central.sonatype.com,ci.dot.net,clojars.org,cloud.elastic.co,cocoapods.org,code.jquery.com,codeload.github.com,conda.anaconda.org,conda.binstar.org,cpan.metacpan.org,cpan.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,data.jsdelivr.com,dc.services.visualstudio.com,deb.debian.org,deb.nodesource.com,debian.map.fastlydns.net,deno.land,dist.nuget.org,dl-cdn.alpinelinux.org,dl.bintray.com,dl.fedoraproject.org,dl.google.com,dl.k8s.io,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.fedoraproject.org,download.java.net,download.opensuse.org,download.oracle.com,download.swift.org,downloads.gradle-dn.com,downloads.haskell.org,ela.st,elastic.co,elastic.dev,esm.sh,fastly.hex.pm,files.pythonhosted.org,fonts.googleapis.com,fonts.gstatic.com,gcr.io,ge.jetbrains.com,gems.rubyforge.org,gems.rubyonrails.org,get-ghcup.haskell.org,get.pnpm.io,getcomposer.org,ghcr.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,gradle.org,haskell.org,hex.pm,host.docker.internal,index.crates.io,index.rubygems.org,jcenter.bintray.com,jdk.java.net,jitpack.io,json-schema.org,json.schemastore.org,jsr.io,keyring.debian.org,keyserver.ubuntu.com,kotlin.bintray.com,lfs.github.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,mcr.microsoft.com,metacpan.org,mirror.archlinux.org,mirror.centos.org,mirrors.fedoraproject.org,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.debian.org,packages.jetbrains.team,packages.microsoft.com,packagist.org,pip.pypa.io,pkg.alpinelinux.org,pkg.go.dev,pkg.machengine.org,pkgs.dev.azure.com,pkgs.k8s.io,playwright.download.prss.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,production.cloudflare.docker.com,productionresultssa0.blob.core.windows.net,productionresultssa1.blob.core.windows.net,productionresultssa10.blob.core.windows.net,productionresultssa11.blob.core.windows.net,productionresultssa12.blob.core.windows.net,productionresultssa13.blob.core.windows.net,productionresultssa14.blob.core.windows.net,productionresultssa15.blob.core.windows.net,productionresultssa16.blob.core.windows.net,productionresultssa17.blob.core.windows.net,productionresultssa18.blob.core.windows.net,productionresultssa19.blob.core.windows.net,productionresultssa2.blob.core.windows.net,productionresultssa3.blob.core.windows.net,productionresultssa4.blob.core.windows.net,productionresultssa5.blob.core.windows.net,productionresultssa6.blob.core.windows.net,productionresultssa7.blob.core.windows.net,productionresultssa8.blob.core.windows.net,productionresultssa9.blob.core.windows.net,proxy.golang.org,pub.dartlang.org,pub.dev,public-code-search.fastmcp.app,pypi.org,pypi.python.org,quay.io,raw.githubusercontent.com,registry.bower.io,registry.hub.docker.com,registry.npmjs.com,registry.npmjs.org,registry.terraform.io,registry.yarnpkg.com,releases.hashicorp.com,repo.anaconda.com,repo.clojars.org,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.hex.pm,repo.maven.apache.org,repo.packagist.org,repo.scala-sbt.org,repo.spring.io,repo.typesafe.com,repo.yarnpkg.com,repo1.maven.org,rubygems.org,rubygems.pkg.github.com,s.symcb.com,s.symcd.com,scala-ci.typesafe.com,security.debian.org,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,sum.golang.org,swift.org,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vault.centos.org,www.cpan.org,www.elastic.co,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com,yum.releases.hashicorp.com,ziglang.org" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ inputs.model }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: |
+ # Copy Copilot session state files to logs folder for artifact collection
+ # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
+ SESSION_STATE_DIR="$HOME/.copilot/session-state"
+ LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
+
+ if [ -d "$SESSION_STATE_DIR" ]; then
+ echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
+ mkdir -p "$LOGS_DIR"
+ cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
+ echo "Session state files copied successfully"
+ else
+ echo "No session-state directory found at $SESSION_STATE_DIR"
+ fi
+ - name: Stop MCP Gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Upload Safe Outputs
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: safe-output
+ path: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ if-no-files-found: warn
+ - name: Ingest agent output
+ id: collect_output
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "*.docker.com,*.docker.io,*.githubusercontent.com,*.hackage.haskell.org,*.jsr.io,*.pythonhosted.org,*.rvm.io,*.vsblob.vsassets.io,adoptium.net,agents-md-generator.fastmcp.app,anaconda.org,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.nuget.org,api.rubygems.org,api.snapcraft.io,apt.llvm.org,apt.releases.hashicorp.com,archive.apache.org,archive.ubuntu.com,archlinux.org,artifacts.elastic.co,auth.docker.io,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,binstar.org,bitbucket.org,bootstrap.pypa.io,builds.dotnet.microsoft.com,builds.hex.pm,bun.sh,bundler.rubygems.org,cache.ruby-lang.org,cdn.azul.com,cdn.cocoapods.org,cdn.hex.pm,cdn.jsdelivr.net,cdn.playwright.dev,cdn.redhat.com,cdn.sheetjs.com,central.sonatype.com,ci.dot.net,clojars.org,cloud.elastic.co,cocoapods.org,code.jquery.com,codeload.github.com,conda.anaconda.org,conda.binstar.org,cpan.metacpan.org,cpan.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,data.jsdelivr.com,dc.services.visualstudio.com,deb.debian.org,deb.nodesource.com,debian.map.fastlydns.net,deno.land,dist.nuget.org,dl-cdn.alpinelinux.org,dl.bintray.com,dl.fedoraproject.org,dl.google.com,dl.k8s.io,dlcdn.apache.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.fedoraproject.org,download.java.net,download.opensuse.org,download.oracle.com,download.swift.org,downloads.gradle-dn.com,downloads.haskell.org,ela.st,elastic.co,elastic.dev,esm.sh,fastly.hex.pm,files.pythonhosted.org,fonts.googleapis.com,fonts.gstatic.com,gcr.io,ge.jetbrains.com,gems.rubyforge.org,gems.rubyonrails.org,get-ghcup.haskell.org,get.pnpm.io,getcomposer.org,ghcr.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,gradle.org,haskell.org,hex.pm,host.docker.internal,index.crates.io,index.rubygems.org,jcenter.bintray.com,jdk.java.net,jitpack.io,json-schema.org,json.schemastore.org,jsr.io,keyring.debian.org,keyserver.ubuntu.com,kotlin.bintray.com,lfs.github.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,mcr.microsoft.com,metacpan.org,mirror.archlinux.org,mirror.centos.org,mirrors.fedoraproject.org,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.debian.org,packages.jetbrains.team,packages.microsoft.com,packagist.org,pip.pypa.io,pkg.alpinelinux.org,pkg.go.dev,pkg.machengine.org,pkgs.dev.azure.com,pkgs.k8s.io,playwright.download.prss.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,production.cloudflare.docker.com,productionresultssa0.blob.core.windows.net,productionresultssa1.blob.core.windows.net,productionresultssa10.blob.core.windows.net,productionresultssa11.blob.core.windows.net,productionresultssa12.blob.core.windows.net,productionresultssa13.blob.core.windows.net,productionresultssa14.blob.core.windows.net,productionresultssa15.blob.core.windows.net,productionresultssa16.blob.core.windows.net,productionresultssa17.blob.core.windows.net,productionresultssa18.blob.core.windows.net,productionresultssa19.blob.core.windows.net,productionresultssa2.blob.core.windows.net,productionresultssa3.blob.core.windows.net,productionresultssa4.blob.core.windows.net,productionresultssa5.blob.core.windows.net,productionresultssa6.blob.core.windows.net,productionresultssa7.blob.core.windows.net,productionresultssa8.blob.core.windows.net,productionresultssa9.blob.core.windows.net,proxy.golang.org,pub.dartlang.org,pub.dev,public-code-search.fastmcp.app,pypi.org,pypi.python.org,quay.io,raw.githubusercontent.com,registry.bower.io,registry.hub.docker.com,registry.npmjs.com,registry.npmjs.org,registry.terraform.io,registry.yarnpkg.com,releases.hashicorp.com,repo.anaconda.com,repo.clojars.org,repo.continuum.io,repo.gradle.org,repo.grails.org,repo.hex.pm,repo.maven.apache.org,repo.packagist.org,repo.scala-sbt.org,repo.spring.io,repo.typesafe.com,repo.yarnpkg.com,repo1.maven.org,rubygems.org,rubygems.pkg.github.com,s.symcb.com,s.symcd.com,scala-ci.typesafe.com,security.debian.org,security.ubuntu.com,services.gradle.org,sh.rustup.rs,skimdb.npmjs.com,static.crates.io,static.rust-lang.org,storage.googleapis.com,sum.golang.org,swift.org,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vault.centos.org,www.cpan.org,www.elastic.co,www.java.com,www.microsoft.com,www.npmjs.com,www.npmjs.org,yarnpkg.com,yum.releases.hashicorp.com,ziglang.org"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
+ await main();
+ - name: Upload sanitized agent output
+ if: always() && env.GH_AW_AGENT_OUTPUT
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: agent-output
+ path: ${{ env.GH_AW_AGENT_OUTPUT }}
+ if-no-files-found: warn
+ - name: Upload engine output files
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: agent_outputs
+ path: |
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ if-no-files-found: ignore
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP Gateway logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
+ if command -v awf &> /dev/null; then
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ else
+ echo 'AWF binary not installed, skipping firewall log summary'
+ fi
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: agent-artifacts
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/agent-stdio.log
+ /tmp/gh-aw/agent/
+ if-no-files-found: ignore
+ # --- Threat Detection (inline) ---
+ - name: Check if detection needed
+ id: detection_guard
+ if: always()
+ env:
+ OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }}
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ run: |
+ if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
+ echo "run_detection=true" >> "$GITHUB_OUTPUT"
+ echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
+ else
+ echo "run_detection=false" >> "$GITHUB_OUTPUT"
+ echo "Detection skipped: no agent outputs or patches to analyze"
+ fi
+ - name: Clear MCP configuration for detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
+ rm -f /home/runner/.copilot/mcp-config.json
+ rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
+ - name: Prepare threat detection files
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
+ for f in /tmp/gh-aw/aw-*.patch; do
+ [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ done
+ echo "Prepared threat detection files:"
+ ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ - name: Setup threat detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Dependency Review"
+ WORKFLOW_DESCRIPTION: "Analyze Dependabot and Renovate dependency update PRs"
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Execute GitHub Copilot CLI
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ id: detection_agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ inputs.model }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_detection_results
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: threat-detection.log
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+ - name: Set detection conclusion
+ id: detection_conclusion
+ if: always()
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }}
+ run: |
+ if [[ "$RUN_DETECTION" != "true" ]]; then
+ echo "conclusion=skipped" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection was not needed, marking as skipped"
+ elif [[ "$DETECTION_SUCCESS" == "true" ]]; then
+ echo "conclusion=success" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection passed successfully"
+ else
+ echo "conclusion=failure" >> "$GITHUB_OUTPUT"
+ echo "success=false" >> "$GITHUB_OUTPUT"
+ echo "Detection found issues"
+ fi
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - safe_outputs
+ if: (always()) && (needs.agent.result != 'skipped')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ outputs:
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: "1"
+ GH_AW_WORKFLOW_NAME: "Dependency Review"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/noop.cjs');
+ await main();
+ - name: Record Missing Tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Dependency Review"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Dependency Review"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_WORKFLOW_ID: "gh-aw-dependency-review"
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[What is this?](https://ela.st/github-ai-tools) | [From workflow: {workflow_name}]({run_url})\\n\\nGive us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.' }}\",\"activationComments\":\"false\"}"
+ GH_AW_GROUP_REPORTS: "false"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+ - name: Handle No-Op Message
+ id: handle_noop_message
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Dependency Review"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
+ GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+
+ pre_activation:
+ runs-on: ubuntu-slim
+ outputs:
+ activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
+ matched_command: ''
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Check team membership for workflow
+ id: check_membership
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_REQUIRED_ROLES: admin,maintainer,write
+ GH_AW_ALLOWED_BOTS: ${{ inputs.allowed-bot-users }},dependabot[bot],renovate[bot]
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_membership.cjs');
+ await main();
+
+ safe_outputs:
+ needs: agent
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ timeout-minutes: 15
+ env:
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_MODEL: "${{ inputs.model }}"
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[What is this?](https://ela.st/github-ai-tools) | [From workflow: {workflow_name}]({run_url})\\n\\nGive us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.' }}\",\"activationComments\":\"false\"}"
+ GH_AW_WORKFLOW_ID: "gh-aw-dependency-review"
+ GH_AW_WORKFLOW_NAME: "Dependency Review"
+ outputs:
+ code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
+ code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"needs-human-review\",\"higher-risk\"],\"max\":3},\"missing_data\":{},\"missing_tool\":{}}"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+ - name: Upload safe output items manifest
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: safe-output-items
+ path: /tmp/safe-output-items.jsonl
+ if-no-files-found: warn
+
diff --git a/.github/workflows/gh-aw-dependency-review.md b/.github/workflows/gh-aw-dependency-review.md
new file mode 100644
index 00000000..f862a90c
--- /dev/null
+++ b/.github/workflows/gh-aw-dependency-review.md
@@ -0,0 +1,283 @@
+---
+inlined-imports: true
+name: "Dependency Review"
+description: "Analyze Dependabot and Renovate dependency update PRs"
+imports:
+ - gh-aw-fragments/elastic-tools.md
+ - gh-aw-fragments/runtime-setup.md
+ - gh-aw-fragments/formatting.md
+ - gh-aw-fragments/rigor.md
+ - gh-aw-fragments/mcp-pagination.md
+ - gh-aw-fragments/messages-footer.md
+ - gh-aw-fragments/safe-output-add-comment-pr.md
+ - gh-aw-fragments/network-ecosystems.md
+engine:
+ id: copilot
+ model: ${{ inputs.model }}
+ concurrency:
+ group: "gh-aw-copilot-dependency-review-${{ github.event.pull_request.number }}"
+on:
+ workflow_call:
+ inputs:
+ model:
+ description: "AI model to use"
+ type: string
+ required: false
+ default: "gpt-5.3-codex"
+ additional-instructions:
+ description: "Repo-specific instructions appended to the agent prompt"
+ type: string
+ required: false
+ default: ""
+ setup-commands:
+ description: "Shell commands to run before the agent starts (dependency install, build, etc.)"
+ type: string
+ required: false
+ default: ""
+ allowed-bot-users:
+ description: "Allowlisted bot actor usernames (comma-separated)"
+ type: string
+ required: false
+ default: "github-actions[bot]"
+ messages-footer:
+ description: "Footer appended to all agent comments and reviews"
+ type: string
+ required: false
+ default: ""
+ secrets:
+ COPILOT_GITHUB_TOKEN:
+ required: true
+ roles: [admin, maintainer, write]
+ bots:
+ - "${{ inputs.allowed-bot-users }}"
+ - "dependabot[bot]"
+ - "renovate[bot]"
+concurrency:
+ group: dependency-review-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+permissions:
+ actions: read
+ contents: read
+ issues: read
+ pull-requests: read
+tools:
+ github:
+ toolsets: [repos, issues, pull_requests, search, actions]
+ bash: true
+ web-fetch:
+safe-outputs:
+ activation-comments: false
+ add-labels:
+ max: 3
+ allowed:
+ - "needs-human-review"
+ - "higher-risk"
+strict: false
+timeout-minutes: 60
+steps:
+ - name: Repo-specific setup
+ if: ${{ inputs.setup-commands != '' }}
+ env:
+ SETUP_COMMANDS: ${{ inputs.setup-commands }}
+ run: eval "$SETUP_COMMANDS"
+---
+
+# Dependency Review Agent
+
+Analyze dependency update pull requests (Dependabot, Renovate) in ${{ github.repository }}. Provide a detailed analysis comment covering changelog highlights, compatibility, risk, and ecosystem-specific checks.
+
+## Context
+
+- **Repository**: ${{ github.repository }}
+- **PR**: #${{ github.event.pull_request.number }} — ${{ github.event.pull_request.title }}
+- **PR Author**: ${{ github.actor }}
+
+## Constraints
+
+This workflow is read-only. You can read files, search code, run commands, and comment on PRs — but your only outputs are an analysis comment and optional labels.
+
+## Instructions
+
+### Step 1: Gather Context
+
+1. Call `generate_agents_md` to get the repository's coding guidelines and conventions. If this fails, continue without it.
+2. Call `pull_request_read` with method `get` on PR #${{ github.event.pull_request.number }} to get full PR details (author, description, branches).
+3. Call `pull_request_read` with method `get_diff` to see exactly what changed.
+4. Call `pull_request_read` with method `get_files` to get the list of changed files.
+
+### Step 2: Identify and Classify Updated Dependencies
+
+Parse the diff to identify each dependency being updated. For each dependency, extract:
+- **Ecosystem**: GitHub Actions, Buildkite plugin, Go module, npm package, Python (pip/Poetry/uv), Maven/Gradle (Java), or other
+- **Package name**: e.g. `actions/checkout`, `golang.org/x/net`, `express`, `requests`
+- **Old version**: tag, SHA, or version before the update
+- **New version**: tag, SHA, or version after the update
+
+Classify each dependency by looking at the files changed:
+- `.github/workflows/*.yml` or `.github/workflows/*.yaml` → **GitHub Actions**
+- `pipeline.yml`, `.buildkite/` files → **Buildkite plugin**
+- `go.mod`, `go.sum` → **Go module**
+- `package.json`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` → **npm/Node**
+- `pyproject.toml`, `requirements*.txt`, `Pipfile*`, `poetry.lock`, `uv.lock` → **Python**
+- `pom.xml`, `build.gradle`, `build.gradle.kts`, `gradle.lockfile` → **Java/Kotlin (Maven/Gradle)**
+- Other manifest files → classify by ecosystem
+
+### Step 3: Analyze Each Dependency
+
+For each updated dependency, perform the following checks:
+
+#### 3a: Commit Verification (GitHub Actions only)
+
+If the action reference uses a commit SHA (e.g. `uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd`):
+
+1. Verify the commit is a verified commit by checking the GitHub API:
+ ```bash
+ gh api repos/{owner}/{repo}/commits/{sha} --jq '.commit.verification.verified'
+ ```
+2. If the commit is **not verified**, flag this prominently. Unverified commits in pinned actions are a supply-chain risk.
+3. Check whether the commit SHA corresponds to a known release tag:
+ ```bash
+ gh api repos/{owner}/{repo}/git/matching-refs/tags --jq '.[].ref' | head -20
+ ```
+ Then verify the tag points to the expected SHA.
+
+#### 3b: Changelog and Release Notes
+
+For dependencies hosted on GitHub, fetch the release notes:
+1. Fetch the release notes for the new version from the dependency's repository:
+ ```bash
+ gh api repos/{owner}/{repo}/releases/tags/{new_tag} --jq '.body' 2>/dev/null
+ ```
+2. If no release exists for the exact tag, check the latest releases:
+ ```bash
+ gh api repos/{owner}/{repo}/releases --jq '.[].tag_name' | head -10
+ ```
+3. For non-GitHub dependencies, check the package registry or changelog files in the source repo when available.
+4. Summarize key changes between the old and new versions, focusing on:
+ - Breaking changes or removed features
+ - New required configuration or changed defaults
+ - Security fixes
+ - Deprecations
+ - Notable new features relevant to how this repo uses the dependency
+
+#### 3c: Usage Analysis
+
+1. Search the repository for all places the dependency is used. The search method depends on the ecosystem:
+ - **GitHub Actions**: `grep -rn '{owner}/{repo}' .github/workflows/ --include='*.yml' --include='*.yaml'`
+ - **Go**: `grep -rn '{module}' --include='*.go'` (look for import statements)
+ - **npm/Node**: `grep -rn "require('{package}')\|from '{package}'" --include='*.js' --include='*.ts' --include='*.mjs' --include='*.cjs'`
+ - **Python**: `grep -rn "import {package}\|from {package}" --include='*.py'`
+ - **Java**: `grep -rn '{groupId}' --include='*.java' --include='*.kt' --include='*.gradle' --include='*.xml'`
+2. For each usage, note:
+ - Which files and modules use it
+ - What APIs, functions, or features are consumed
+ - For GitHub Actions: what inputs are passed and outputs consumed
+ - For GitHub Actions: what events trigger the workflow
+
+3. Cross-reference the usage against the changelog:
+ - Are any APIs, inputs, or features used by this repo deprecated or removed in the new version?
+ - Are there breaking changes to consumed interfaces?
+ - Are there new required configuration options that are not provided?
+
+#### 3d: Testability Assessment
+
+1. Check the trigger events for each workflow that uses the updated dependency.
+2. If a workflow is **only** triggered by `push` (to main/default branch), `release`, `schedule`, or `workflow_dispatch`, it **cannot be validated by the PR itself**. Flag this as higher risk.
+3. If a workflow is triggered by `pull_request` or `pull_request_target`, it can be exercised in the PR context.
+
+#### 3e: Pin Format Check (Buildkite plugins)
+
+For Buildkite plugin updates:
+1. Check if the update moves from a SHA-pinned version to a mutable tag (higher risk).
+2. Check if the update moves from one mutable tag to another mutable tag (moderate risk).
+3. SHA-to-SHA or tag-to-SHA-pinned updates are preferred.
+
+#### 3f: Ecosystem-Specific Guidance
+
+Apply the following additional checks based on the dependency ecosystem:
+
+**Go modules:**
+- Check if this is a major version bump (e.g. v1 → v2) — Go major versions change the import path, which is a breaking change requiring code updates across the repo.
+- For indirect dependency updates, note that these are transitive and generally lower risk.
+- Check for `// Deprecated:` annotations in the module if accessible.
+
+**npm / Node packages:**
+- Check if this is a major semver bump — major versions typically signal breaking changes.
+- Look for peer dependency conflicts that may arise from the update.
+- For `devDependencies`, note that these only affect development and CI, not production.
+
+**Python packages (pip, Poetry, uv):**
+- Check if this is a major version bump — may indicate breaking API changes.
+- Check for minimum Python version requirements that may have changed.
+- For packages with native extensions (e.g. `numpy`, `cryptography`), note potential build or platform compatibility changes.
+
+**Java / Kotlin (Maven, Gradle):**
+- Check if this is a major version bump — may indicate breaking API changes.
+- Note if the groupId or artifactId changed (dependency relocation).
+- For Spring or framework dependencies, check for minimum JDK version changes.
+
+### Step 4: Determine Labels
+
+Based on the analysis, determine if labels should be applied:
+
+- **`needs-human-review`**: Apply when ANY of these conditions are met:
+ - A dependency update introduces breaking changes that affect this repo's usage
+ - A GitHub Actions commit SHA is not verified
+ - A Buildkite plugin moves from SHA-pinned to mutable tag, or between mutable tags
+ - The changelog indicates breaking changes
+ - A major version bump in any ecosystem (e.g. v1 → v2 in Go, major semver in npm/Python/Java)
+
+- **`higher-risk`**: Apply when:
+ - The updated dependency is used only in workflows triggered by push-to-main, release, schedule, or workflow_dispatch (cannot be validated in PR context)
+
+Only apply `needs-human-review` and `higher-risk` labels.
+
+### Step 5: Post Analysis Comment
+
+Call `add_comment` on the PR with a structured analysis. Use the following format:
+
+> ## Dependency Update Analysis
+>
+> **Summary**: [One-line summary of the update and overall risk assessment]
+>
+> ### [Dependency 1: package vOLD → vNEW]
+>
+> **Ecosystem**: [GitHub Actions / Go / npm / Python / Java / Buildkite / other]
+>
+> | Check | Result |
+> | --- | --- |
+> | Breaking changes | ✅ None found / ⚠️ Found (details below) |
+> | Testable in PR | ✅ Yes / ⚠️ No — workflow only runs on [events] |
+> | Commit verified | ✅ Yes / ⚠️ No *(GitHub Actions only)* |
+> | Pin format | ✅ SHA-pinned / ⚠️ Mutable tag *(GitHub Actions / Buildkite only)* |
+>
+> Only include rows relevant to the dependency ecosystem. For example, "Commit verified" and "Pin format" only apply to GitHub Actions and Buildkite.
+>
+>
+> Changelog highlights (vOLD → vNEW)
+>
+> [Key changes from release notes]
+>
+>
+>
+> Usage in this repository
+>
+> [List of files/modules using this dependency and relevant APIs/inputs/outputs]
+>
+>
+>
+> Compatibility assessment
+>
+> [Analysis of whether current usage is compatible with the new version, including ecosystem-specific notes]
+>
+>
+> ### Labels Applied
+> [List of labels applied and why, or "No labels applied"]
+
+If the analysis found no issues, keep the comment concise — do not pad with unnecessary detail.
+
+### Step 6: Apply Labels
+
+If any labels were determined in Step 4, call `add_labels` to apply them to the PR.
+
+${{ inputs.additional-instructions }}
diff --git a/.github/workflows/trigger-dependency-review.yml b/.github/workflows/trigger-dependency-review.yml
new file mode 100644
index 00000000..eaa740f4
--- /dev/null
+++ b/.github/workflows/trigger-dependency-review.yml
@@ -0,0 +1,21 @@
+# This file is auto-generated by scripts/dogfood.sh. Do not edit directly.
+# Edit gh-agent-workflows/dependency-review/example.yml and run 'make compile' to regenerate.
+name: Trigger Dependency Review
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ actions: read
+ contents: read
+ issues: write
+ pull-requests: write
+
+jobs:
+ run:
+ if: >-
+ github.event.pull_request.user.login == 'dependabot[bot]' ||
+ github.event.pull_request.user.login == 'renovate[bot]'
+ uses: ./.github/workflows/gh-aw-dependency-review.lock.yml
+ secrets:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
diff --git a/docs/workflows/gh-agent-workflows.md b/docs/workflows/gh-agent-workflows.md
index 1039ea5b..6c8abf57 100644
--- a/docs/workflows/gh-agent-workflows.md
+++ b/docs/workflows/gh-agent-workflows.md
@@ -55,6 +55,7 @@ These pair together: a Scheduled Audit finds problems, a Scheduled Fix resolves
| Workflow | Trigger | Description |
| --- | --- | --- |
+| [Dependency Review](gh-agent-workflows/dependency-review.md) | Dependabot/Renovate PRs | Analyze dependency updates across ecosystems with changelog, compatibility, and risk checks |
| [Duplicate Issue Detector](gh-agent-workflows/duplicate-issue-detector.md) | New issues | Detect duplicate issues and comment with links |
| [Issue Triage](gh-agent-workflows/issue-triage.md) | New issues | Investigate and provide implementation plans |
| [Issue Fixer](gh-agent-workflows/issue-fixer.md) | New issues | Triage plus automatic draft PR for straightforward fixes |
diff --git a/gh-agent-workflows/README.md b/gh-agent-workflows/README.md
index 53f44a06..a05753fd 100644
--- a/gh-agent-workflows/README.md
+++ b/gh-agent-workflows/README.md
@@ -37,6 +37,9 @@ Pre-built workflows with domain-specific prompts. These import the same base fra
- [Text Beautifier](text-beautifier/) — fix text-auditor issues
- [Code Duplication Fixer](code-duplication-fixer/) — fix code-duplication-detector issues
+**Event-driven** (comment on PRs):
+- [Dependency Review](dependency-review/) — analyze Dependabot/Renovate dependency update PRs across ecosystems (GitHub Actions, Go, npm, Python, Java, Buildkite)
+
**Research assistants**:
- [Deep Research](deep-research/) — issue-comment deep research with web search/fetch and optional PR creation
diff --git a/gh-agent-workflows/dependency-review/README.md b/gh-agent-workflows/dependency-review/README.md
new file mode 100644
index 00000000..7d7b58f1
--- /dev/null
+++ b/gh-agent-workflows/dependency-review/README.md
@@ -0,0 +1,44 @@
+# Dependency Review
+
+Analyze Dependabot and Renovate dependency update PRs.
+
+## How it works
+
+Triggered when Dependabot or Renovate opens or updates a PR. Classifies each dependency by ecosystem (GitHub Actions, Go, npm, Python, Java, Buildkite, etc.), runs shared checks (changelog, usage analysis, compatibility, testability), and applies ecosystem-specific checks where relevant. Posts a structured analysis comment and optionally labels the PR `needs-human-review` or `higher-risk`.
+
+## Quick Install
+
+```bash
+mkdir -p .github/workflows && curl -sL \
+ https://raw.githubusercontent.com/elastic/ai-github-actions/v0/gh-agent-workflows/dependency-review/example.yml \
+ -o .github/workflows/dependency-review.yml
+```
+
+See [example.yml](example.yml) for the full workflow file.
+
+## Trigger
+
+| Event | Types | Condition |
+| --- | --- | --- |
+| `pull_request` | `opened`, `synchronize`, `reopened` | PR author is `dependabot[bot]` or `renovate[bot]` |
+
+## Inputs
+
+| Input | Description | Required | Default |
+| --- | --- | --- | --- |
+| `additional-instructions` | Repo-specific instructions appended to the agent prompt | No | `""` |
+| `setup-commands` | Shell commands run before the agent starts | No | `""` |
+| `allowed-bot-users` | Allowlisted bot actor usernames (comma-separated) | No | `github-actions[bot]` |
+
+## Safe Outputs
+
+- `add-comment` — post an analysis comment on the PR (max 1)
+- `add-labels` — label the PR when human review or higher risk is detected (max 3)
+
+## Manual usage with mention-in-pr
+
+You can also analyze any dependency update PR on demand using `mention-in-pr`. Comment on the PR with:
+
+```
+/ai Analyze this dependency update PR: classify each dependency by ecosystem, check for breaking changes, summarize changelog highlights, assess usage compatibility, and note testability in PR context.
+```
diff --git a/gh-agent-workflows/dependency-review/example.yml b/gh-agent-workflows/dependency-review/example.yml
new file mode 100644
index 00000000..74bb07a3
--- /dev/null
+++ b/gh-agent-workflows/dependency-review/example.yml
@@ -0,0 +1,19 @@
+name: Dependency Review
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ actions: read
+ contents: read
+ issues: write
+ pull-requests: write
+
+jobs:
+ run:
+ if: >-
+ github.event.pull_request.user.login == 'dependabot[bot]' ||
+ github.event.pull_request.user.login == 'renovate[bot]'
+ uses: elastic/ai-github-actions/.github/workflows/gh-aw-dependency-review.lock.yml@v0
+ secrets:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}