Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 5 additions & 1 deletion .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ permissions:
jobs:
sonarcloud:
name: SonarCloud Analysis
uses: ByronWilliamsCPA/.github/.github/workflows/python-sonarcloud.yml@7d12f5486ab5c856397ebaa4acd3c99ca385227c # main
uses: ByronWilliamsCPA/.github/.github/workflows/python-sonarcloud.yml@4bd2d7c207a7fcf2dfcec8416c805d06d745c241 # main

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository workflow =="
git ls-files .github/workflows/sonarcloud.yml || true
if [ -f .github/workflows/sonarcloud.yml ]; then
  nl -ba .github/workflows/sonarcloud.yml | sed -n '1,220p'
fi

echo
echo "== available git references for pinned reusable workflow =="
git ls-remote --heads --tags https://github.com/ByronWilliamsCPA/.github.git 4bd2d7c207a7fcf2dfcec8416c805d06d745c241 || true
git ls-remote https://github.com/ByronWilliamsCPA/.github.git main || true

echo
echo "== raw reusable workflow around checkout and job permissions =="
python3 - <<'PY'
import urllib.request
pinned = "4bd2d7c207a7fcf2dfcec8416c805d06d745c241"
url = f"https://raw.githubusercontent.com/ByronWilliamsCPA/.github/{pinned}/.github/workflows/python-sonarcloud.yml"
try:
    with urllib.request.urlopen(url, timeout=20) as r:
        data = r.read().decode()
    lines = data.splitlines()
    for i, line in enumerate(lines, 1):
        if "actions/checkout" in line or "permissions:" in line or "pull-requests:" in line or "sonar-python-version" in line:
            start=max(1,i-8); end=min(len(lines), i+10)
            print(f"--- matches line {i} range {start}-{end} ---")
            for j in range(start, end+1):
                print(f"{j:4}: {lines[j-1]}")
except Exception as e:
    print(f"ERROR fetching {url}: {e.__class__.__name__}: {e}")
PY

echo
echo "== checkout input defaults around persist-credentials =="
python3 - <<'PY'
import urllib.request
base = "https://raw.githubusercontent.com/actions/checkout"
for ref in ["v4", "main", "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"]:
    try:
        url = f"{base}/{ref}/action.yml"
        data = urllib.request.urlopen(url, timeout=20).read().decode()
        lines = data.splitlines()
        print(f"--- action.yml {ref} ---")
        for i,line in enumerate(lines,1):
            if "persist-credentials" in line:
                print(f"{i:4}: {line}")
    except Exception as e:
        print(f"ERROR for checkout {ref}: {e}")
PY

Repository: ByronWilliamsCPA/rag-processor

Length of output: 267


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== check workflow trigger and job contents =="
nl -ba .github/workflows/sonarcloud.yml | sed -n '1,220p'

echo
echo "== resolve exact reusable workflow workflow file by git API =="
git ls-remote https://github.com/ByronWilliamsCPA/.github.git 4bd2d7c207a7fcf2dfcec8416c805d06d745c241 || true
git ls-remote https://github.com/ByronWilliamsCPA/.github.git refs/tags/main || true
git ls-remote https://github.com/ByronWilliamsCPA/.github.git refs/heads/main || true

Repository: ByronWilliamsCPA/rag-processor

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository workflow =="
awk '{printf "%5d: %s\n", NR, $0}' .github/workflows/sonarcloud.yml | sed -n '1,220p'

echo
echo "== raw reusable workflow around checkout, permissions, and inputs =="
python3 - <<'PY'
import urllib.request
pinned = "4bd2d7c207a7fcf2dfcec8416c805d06d745c241"
url = f"https://raw.githubusercontent.com/ByronWilliamsCPA/.github/{pinned}/.github/workflows/python-sonarcloud.yml"
try:
    with urllib.request.urlopen(url, timeout=20) as r:
        data = r.read().decode()
except Exception as e:
    print(f"ERROR fetching {url}: {e.__class__.__name__}: {e}")
    raise SystemExit(1)
lines = data.splitlines()
for i, line in enumerate(lines, 1):
    if any(k in line for k in ("actions/checkout", "permissions:", "'pull-requests:', 'pull-requests:", "pull-requests:", "sonar-python-version", "GITHUB_TOKEN", "GITHUB_OUTPUT")):
        print(f"--- matches line {i} ---")
        for j in range(max(1, i-8), min(len(lines), i+10)+1):
            print(f"{j:4}: {lines[j-1]}")

echo
echo "== checkout persist-credentials input description from action.yml =="
python3 - <<'PY'
import urllib.request
base = "https://raw.githubusercontent.com/actions/checkout"
for ref in ["v4"]:
    try:
        data = urllib.request.urlopen(f"{base}/{ref}/action.yml", timeout=20).read().decode()
        print(f"--- {ref} ---")
        for i, line in enumerate(data.splitlines(), 1):
            if "persist-credentials" in line or line.strip() == "description:":
                print(f"{i:4}: {line}")
    except Exception as e:
        print(f"ERROR checkout {ref}: {e}")
PY

Repository: ByronWilliamsCPA/rag-processor

Length of output: 2359


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== raw reusable workflow around sensitive lines =="
python3 - <<'PY'
import urllib.request
pinned = "4bd2d7c207a7fcf2dfcec8416c805d06d745c241"
url = f"https://raw.githubusercontent.com/ByronWilliamsCPA/.github/{pinned}/.github/workflows/python-sonarcloud.yml"
try:
    lines = urllib.request.urlopen(url, timeout=20).read().decode().splitlines()
except Exception as e:
    print(f"ERROR fetching {url}: {e.__class__.__name__}: {e}")
else:
    for i, line in enumerate(lines, 1):
        if "actions/checkout" in line or "GITHUB_TOKEN" in line or "pull-requests" in line or "sonar-python-version" in line or "uv sync" in line or "pytest" in line or "GH_TOKEN" in line:
            start=max(1,i-10); end=min(len(lines), i+12)
            print(f"--- matches line {i} range {start}-{end} ---")
            for j in range(start, end+1):
                print(f"{j:4}: {lines[j-1]}")

echo
echo "== GitHub action checkout persist-credentials from v4 == "
python3 - <<'PY'
import urllib.request
data = urllib.request.urlopen("https://raw.githubusercontent.com/actions/checkout/v4/action.yml", timeout=20).read().decode()
for i, line in enumerate(data.splitlines(), 1):
    if "persist-credentials" in line:
        print(f"{i:4}: {line}")
PY

Repository: ByronWilliamsCPA/rag-processor

Length of output: 353


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== reusable workflow relevant sections =="
python3 - <<'PY'
import urllib.request
sha = "4bd2d7c207a7fcf2dfcec8416c805d06d745c241"
url = f"https://raw.githubusercontent.com/ByronWilliamsCPA/.github/{sha}/.github/workflows/python-sonarcloud.yml"
try:
    text = urllib.request.urlopen(url, timeout=20).read().decode()
except Exception as e:
    print(f"ERROR fetching {url}: {e.__class__.__name__}: {e}")
    raise SystemExit(1)
lines = text.splitlines()
keywords = ["actions/checkout", "persist-credentials", "GITHUB_TOKEN", "pull-requests", "uv sync", "pytest", "sonar-python-version", "GH_TOKEN"]
for i, line in enumerate(lines, 1):
    if any(k in line for k in keywords):
        start=max(1,i-10); end=min(len(lines), i+12)
        print(f"--- matches line {i} range {start}-{end} ---")
        for j in range(start, end+1):
            print(f"{j:4}: {lines[j-1]}")
PY

echo
echo "== actions/checkout persist-credentials reference from v4 =="
python3 - <<'PY'
import urllib.request
text = urllib.request.urlopen("https://raw.githubusercontent.com/actions/checkout/v4/action.yml", timeout=20).read().decode()
for i, line in enumerate(text.splitlines(), 1):
    if "persist-credentials" in line:
        print(f"{i:4}: {line}")
PY

Repository: ByronWilliamsCPA/rag-processor

Length of output: 511


🌐 Web query:

GitHub actions checkout v4 persist-credentials default true action.yml

💡 Result:

In the official action.yml configuration for the GitHub Actions checkout action, the default value for the persist-credentials input is true [1][2]. When persist-credentials is set to its default value (true), the authentication token or SSH key used to clone the repository is configured in the local Git configuration. This allows subsequent scripts in your workflow job to run authenticated Git commands [3][4]. The token is automatically removed during the post-job cleanup phase [3][2]. If you wish to opt out of this behavior—for example, to prevent the credential from being stored in the local Git configuration—you can explicitly set persist-credentials to false in your workflow file [3][4]. While there have been community discussions and pull requests proposing to change this default to false for security hardening [5], it remains true in the official v4 and v6 releases of the action [1][6].

Citations:


Sensitive Data Exposure (CWE-522): Insufficiently Protected Credentials

Reachability: Internal

Disable persisted credentials in the reusable workflow checkout.

This job grants pull-requests: write before the pinned reusable workflow checkout persists GITHUB_TOKEN in Git credentials by default. This token remains available until post-job cleanup. Set persist-credentials: false on the checkout step in ByronWilliamsCPA/.github/.github/workflows/python-sonarcloud.yml, or pin a reusable-workflow revision that already disables it.

🤖 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/sonarcloud.yml at line 32, Update the python-sonarcloud
reusable workflow checkout to disable credential persistence by configuring its
checkout step with persist-credentials set to false. Alternatively, update the
pinned reusable-workflow revision to one that already applies this setting,
while preserving the existing workflow permissions.

Sources: Coding guidelines, Path instructions, Learnings, MCP tools

with:
python-version: '3.12'
# Every version requires-python (">=3.11,<3.15") admits, not just the build version.
# SonarPython gates version-specific rules on ALL declared versions, so declaring only
# 3.12 raised PEP 695 rules (S6794/S6796) against code that must still run on 3.11.
sonar-python-version: '3.11,3.12,3.13,3.14'
source-directory: 'src/rag_processor'
sonar-organization: 'byronwilliamscpa'
sonar-project-key: 'ByronWilliamsCPA_rag-processor'
Expand Down
7 changes: 5 additions & 2 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ sonar.sources=src/
# Test directories (comma-separated)
sonar.tests=tests/

# Python version
sonar.python.version=3.12
# Python versions the SOURCE supports, matching requires-python (">=3.11,<3.15").
# CI overrides this via -Dsonar.python.version from the sonar-python-version workflow input,
# so this key only affects local and IDE (SonarLint) analysis. Keep the two in sync, or
# SonarLint keeps raising the 3.12+ rules that CI no longer does.
sonar.python.version=3.11,3.12,3.13,3.14

# =============================================================================
# Test Coverage Configuration
Expand Down
Loading