Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/use-pr-linker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Auto link PR to Issues

on:
pull_request_target:
types:
- opened
- edited
- synchronize
- reopened
- closed
Comment on lines +3 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch and examine the external workflow to understand its permissions requirements
curl -s https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml | head -50

Repository: mosip/audit-manager

Length of output: 1283


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect local workflow
echo "== Local: .github/workflows/use-pr-linker.yml =="
nl -ba .github/workflows/use-pr-linker.yml | sed -n '1,200p'

echo
echo "== Local: search for secret/ref passing =="
rg -n "mosip/kattu|ACTION_PAT|workflow_call|uses:" .github/workflows/use-pr-linker.yml || true

# 2) Inspect external workflow more thoroughly
URL="https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml"

echo
echo "== External: head (first 220 lines) =="
curl -s "$URL" | nl -ba | sed -n '1,220p'

echo
echo "== External: grep for ACTION_PAT usage and fork-related checks =="
curl -s "$URL" | rg -n "ACTION_PAT|secrets\.ACTION_PAT|fork|fromFork|pull_request\.head|head_repo|repository_owner|is_fork|base_repo|permissions" || true

echo
echo "== External: tail (last 220 lines) =="
curl -s "$URL" | nl -ba | tail -n 220

Repository: mosip/audit-manager

Length of output: 173


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Local: .github/workflows/use-pr-linker.yml =="
cat -n .github/workflows/use-pr-linker.yml | sed -n '1,220p'

echo
echo "== Local: search for uses/ref + secret passing =="
rg -n "mosip/kattu|ACTION_PAT|workflow_call|secrets:|secrets\.ACTION_PAT|uses:" .github/workflows/use-pr-linker.yml || true

echo
echo "== External: full text is unknown size; search by patterns =="
URL="https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml"
curl -s "$URL" > /tmp/link-pr-to-issue.yml

echo
echo "== External: occurrences =="
rg -n "ACTION_PAT|secrets\.ACTION_PAT|fork|fromFork|is_fork|head_repo|head.*repo|pull_request\.head|repository_owner|base_repo|permissions" /tmp/link-pr-to-issue.yml || true

echo
echo "== External: show lines around ACTION_PAT usage =="
rg -n "ACTION_PAT|secrets\.ACTION_PAT" -n /tmp/link-pr-to-issue.yml | head -n 50 || true
# Print a context window around first 3 matches (if any)
python3 - <<'PY'
import re
path="/tmp/link-pr-to-issue.yml"
with open(path,'r',encoding='utf-8') as f:
    lines=f.readlines()
idx=[]
for i,l in enumerate(lines, start=1):
    if "ACTION_PAT" in l:
        idx.append(i)
for i in idx[:3]:
    start=max(1,i-25); end=min(len(lines), i+40)
    print(f"\n--- {path}: lines {start}-{end} (match at {i}) ---")
    for j in range(start, end+1):
        print(f"{j}\t{lines[j-1].rstrip()}")
PY

Repository: mosip/audit-manager

Length of output: 7471


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Local: file length =="
wc -l .github/workflows/use-pr-linker.yml

echo
echo "== Local: full file =="
cat -n .github/workflows/use-pr-linker.yml

echo
echo "== External: look for any fork/untrusted checks =="
curl -s "https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml" > /tmp/link-pr-to-issue.yml
rg -n "fork|fromFork|is_fork|head_repo|head\.repo|pull_request\.head|repository_owner|base_repo" /tmp/link-pr-to-issue.yml || true

echo
echo "== External: where GH_TOKEN is used (context) =="
rg -n "GH_TOKEN: \$\{\{ secrets\.ACTION_PAT \}\}|Authorization: Bearer \$GH_TOKEN|issues/comments|api.github.com/search/issues|curl -s -X (POST|PUT|PATCH)|curl -s -X DELETE" /tmp/link-pr-to-issue.yml

echo
echo "== External: show around first POST/PUT/PATCH (if any) =="
python3 - <<'PY'
import re
path="/tmp/link-pr-to-issue.yml"
with open(path,'r',encoding='utf-8') as f: lines=f.readlines()
targets=[]
for i,l in enumerate(lines, start=1):
    if re.search(r'curl -s -X (POST|PUT|PATCH)', l):
        targets.append(i)
for i in targets[:5]:
    s=max(1,i-20); e=min(len(lines), i+25)
    print(f"\n--- {path}: lines {s}-{e} (match at {i}) ---")
    for j in range(s,e+1):
        print(f"{j}\t{lines[j-1].rstrip()}")
PY

Repository: mosip/audit-manager

Length of output: 1124


🏁 Script executed:

#!/bin/bash
set -euo pipefail

URL="https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml"
curl -s "$URL" > /tmp/link-pr-to-issue.yml

echo "== External: check for any fork/untrusted gating patterns =="
rg -n "fork|fromFork|is_fork|head\.repo|base\.repo|repository_owner|event\.pull_request\.head" /tmp/link-pr-to-issue.yml || true

echo
echo "== External: locate comment creation / POST usage =="
rg -n "curl -s -X POST|issues/comments" /tmp/link-pr-to-issue.yml || true

echo
python3 - <<'PY'
import re
path="/tmp/link-pr-to-issue.yml"
lines=open(path,'r',encoding='utf-8').read().splitlines()
# find likely comment/PR-link marker logic
patterns=["MARKER=","pr-linker:","pr-linker:","<!-- pr-linker:","issues/comments","COMMENT_ID","SOURCE_REPO","ISSUE_REPO","REPO="]
for p in patterns:
    hits=[i+1 for i,l in enumerate(lines) if p in l]
    if hits:
        print(f"\n-- first hits for {p} at lines {hits[:5]} --")
        i=hits[0]
        s=max(1,i-35); e=min(len(lines), i+70)
        for j in range(s,e+1):
            print(f"{j}\t{lines[j-1]}")
PY

Repository: mosip/audit-manager

Length of output: 36974


🏁 Script executed:

#!/bin/bash
set -euo pipefail

URL="https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml"
curl -s "$URL" > /tmp/link-pr-to-issue.yml

echo "== External: search for any POST (including multiline -X POST) and comment creation =="
rg -n "curl -s.*-X\s*POST|X POST|issues/.*/comments\?per_page|issues/\$NUMBER/comments" /tmp/link-pr-to-issue.yml || true

echo
echo "== External: show section around where it likely posts comment (first match on issues/$NUMBER/comments without -X DELETE) =="
python3 - <<'PY'
import re
path="/tmp/link-pr-to-issue.yml"
lines=open(path,'r',encoding='utf-8').read().splitlines()
# find first occurrence of "issues/$NUMBER/comments" and show nearby lines
for i,l in enumerate(lines):
    if "issues/$NUMBER/comments" in l and "-X DELETE" not in l:
        start=max(0,i-40); end=min(len(lines)-1,i+80)
        print(f"--- lines {start+1}-{end+1} around first non-delete use at {i+1} ---")
        for j in range(start,end+1):
            print(f"{j+1}\t{lines[j]}")
        break
PY

echo
echo "== External: check for any explicit fork/untrusted checks around PR events =="
rg -n "fork|fromFork|is_fork|head_repo|base_repo|repository_owner|pull_request\.head|security|untrusted|trusted" /tmp/link-pr-to-issue.yml || true

Repository: mosip/audit-manager

Length of output: 394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

URL="https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml"
curl -s "$URL" > /tmp/link-pr-to-issue.yml

echo "== External: grep for POST usage (comment creation) =="
rg -n "curl\s+-s\s+-w.*-X\s+POST|curl.*-X\s+POST|issues/comments\?" /tmp/link-pr-to-issue.yml || true

echo
echo "== External: show around CREATE_RESPONSE (-X POST) =="
python3 - <<'PY'
import re
path="/tmp/link-pr-to-issue.yml"
lines=open(path,'r',encoding='utf-8').read().splitlines()
create=[]
for i,l in enumerate(lines, start=1):
    if "CREATE_RESPONSE" in l or "-X POST" in l:
        create.append(i)
print("matches:", create[:10])
start_line = create[0] if create else None
if start_line:
    s=max(1,start_line-80); e=min(len(lines), start_line+120)
    for j in range(s,e+1):
        print(f"{j}\t{lines[j-1]}")
PY

echo
echo "== External: search for any fork/untrusted gating =="
rg -n "fork|fromFork|is_fork|head_repo|base_repo|repository_owner|pull_request\.head|security|untrusted|trusted" /tmp/link-pr-to-issue.yml || true

echo
echo "== External: search for any validation of issue targets from PR body =="
rg -n "grep -Eo '([a-zA-Z0-9_.-]+/|cut -d'#'|ISSUES=|ISSUE_COUNT|SOURCE_REPO|REPO=\" /tmp/link-pr-to-issue.yml || true

Repository: mosip/audit-manager

Length of output: 4638


🏁 Script executed:

#!/bin/bash
set -euo pipefail

URL="https://raw.githubusercontent.com/mosip/kattu/develop/.github/workflows/link-pr-to-issue.yml"
curl -s "$URL" > /tmp/link-pr-to-issue.yml

python3 - <<'PY'
import re
path="/tmp/link-pr-to-issue.yml"
lines=open(path,'r',encoding='utf-8').read().splitlines()

def show_around(needle, radius=60, max_print=220):
    for i,l in enumerate(lines):
        if needle in l:
            s=max(0,i-radius); e=min(len(lines)-1,i+radius)
            print(f"\n--- around '{needle}' (lines {s+1}-{e+1}) ---")
            for j in range(s,e+1):
                print(f"{j+1}\t{lines[j]}")
            return True
    return False

needles=[
    "ISSUES=$(echo \"$BODY\"",
    "grep -Eo '([a-zA-Z0-9_.-]+\\/[a-zA-Z0-9_.-]+)?#[0-9]+'",
    "if [[ \"$ISSUE\" == *\"/\"* ]]; then",
    "REPO=\"$SOURCE_REPO\"",
    "Processing $REPO#$NUMBER",
]
for n in needles:
    ok=show_around(n, radius=55)
    if not ok:
        print(f"\n--- needle not found: {n} ---")
PY

echo "== External: grep for any comparisons between REPO and SOURCE_REPO =="
rg -n "REPO.*SOURCE_REPO|SOURCE_REPO.*REPO|if.*REPO|allow|whitelist" /tmp/link-pr-to-issue.yml || true

Repository: mosip/audit-manager

Length of output: 19811


Major security risk: pull_request_target passing ACTION_PAT to mutable external workflow enables fork-driven issue/comment writes.

  • .github/workflows/use-pr-linker.yml runs on pull_request_target and passes secrets.ACTION_PAT into mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop (mutable ref), with issues: write permissions.
  • The called workflow requires secrets.ACTION_PAT and uses it as GH_TOKEN to search, delete, PATCH, and POST issue comments via the GitHub API.
  • It derives the target REPO/NUMBER from PR title/body: it accepts owner/repo#number (or defaults to the base repo) and then uses https://api.github.com/repos/$REPO/issues/$NUMBER/comments...—there’s no fork/untrusted-source gating shown before using the PAT.

Verify whether pull_request_target is necessary for fork PRs; if not, switch to pull_request. If it must remain, gate secret usage to same-repo PRs only, pin the external workflow to a commit SHA (or vendor it), and restrict comment targets to the base repo.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 3-10: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/use-pr-linker.yml around lines 3 - 10, The workflow
exposes secrets to untrusted forks because it uses pull_request_target and
forwards secrets.ACTION_PAT to the external mutable workflow
mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop (used as GH_TOKEN)
while deriving REPO/NUMBER from PR title/body; to fix, change the trigger from
pull_request_target to pull_request if you don't need elevated permissions for
fork PRs, otherwise stop passing ACTION_PAT to mutable external workflows and
instead pin the called workflow to a commit SHA (or vendor its steps), add a
guard that verifies github.event.pull_request.base.repo.full_name ===
github.repository (i.e., only same-repo PRs) before exposing secrets and
restrict permissions to the minimum necessary (avoid issues: write or remove
destructive scopes), and validate/whitelist the derived REPO/NUMBER so comments
can only target the base repo.


permissions:
contents: read
issues: write
pull-requests: read

jobs:
call-linker:
uses: mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop
secrets:
ACTION_PAT: ${{ secrets.ACTION_PAT }}
Comment on lines +19 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the latest commit SHA for the develop branch of mosip/kattu
curl -s https://api.github.com/repos/mosip/kattu/commits/develop | jq -r '.sha'

Repository: mosip/audit-manager

Length of output: 104


Critical: Pin reusable workflow to an immutable commit SHA instead of @develop.

This workflow runs on pull_request_target and passes secrets.ACTION_PAT to mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop; using a mutable ref allows the called workflow to change and potentially exfiltrate/abuse the secret. Pin it to a commit SHA (e.g., 6bb3c38b8f5643b906a0d7f4baaede996766b71e).

🔒 Proposed fix
 jobs:
   call-linker:
-    uses: mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop
+    uses: mosip/kattu/.github/workflows/link-pr-to-issue.yml@6bb3c38b8f5643b906a0d7f4baaede996766b71e
     secrets:
       ACTION_PAT: ${{ secrets.ACTION_PAT }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop
secrets:
ACTION_PAT: ${{ secrets.ACTION_PAT }}
uses: mosip/kattu/.github/workflows/link-pr-to-issue.yml@6bb3c38b8f5643b906a0d7f4baaede996766b71e
secrets:
ACTION_PAT: ${{ secrets.ACTION_PAT }}
🧰 Tools
🪛 zizmor (1.25.2)

[error] 19-19: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/use-pr-linker.yml around lines 19 - 21, Replace the
mutable ref in the reusable workflow call so the "uses:
mosip/kattu/.github/workflows/link-pr-to-issue.yml@develop" line pins to an
immutable commit SHA (e.g., replace "`@develop`" with the specific commit SHA like
"`@6bb3c38b8f5643b906a0d7f4baaede996766b71e`") to prevent remote changes from
getting executed with the injected secret ACTION_PAT; ensure the "secrets:
ACTION_PAT" mapping is left intact after updating the ref.

Loading