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
21 changes: 21 additions & 0 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,24 @@ jobs:
echo "::error::Planning/internal docs detected in PR"
exit 1
fi

semgrep:
name: Semgrep (SAST)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v7
with:
enable-cache: true

- name: Install Python 3.12
run: uv python install 3.12

- name: Semgrep scan
run: >
uv run --with semgrep semgrep scan
--config p/python
--config p/security-audit
--config p/command-injection
--error --metrics off src/
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,12 @@ repos:
language: script
types: [file]
pass_filenames: false

# Semgrep SAST — same rulesets as the CI security-scan job. Runs via uv
# (ephemeral, no project dep) and scans src/ when any source file changes.
- id: semgrep
name: Semgrep (SAST)
entry: uv run --with semgrep semgrep scan --config p/python --config p/security-audit --config p/command-injection --error --metrics off --quiet src/
language: system
files: ^src/.*\.py$
pass_filenames: false
12 changes: 10 additions & 2 deletions src/phantom/cli/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,18 @@ def _parse_version(tag: str) -> tuple[int, ...]:


def _fetch_json(url: str) -> dict | None:
"""Fetch JSON from a URL. Returns None on any failure."""
"""Fetch JSON from an https URL. Returns None on any failure.

The scheme is pinned to https so a non-https value (e.g. a ``file://`` URL)
can never be handed to urlopen, which otherwise supports local-file reads.
All callers pass hardcoded GitHub API constants; this guards future callers.
"""
if not url.startswith("https://"):
return None
req = Request(url, headers={"User-Agent": f"phantom-audio/{__version__}"})
try:
with urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
# url is https-pinned above; all callers pass hardcoded GitHub API constants
with urlopen(req, timeout=REQUEST_TIMEOUT) as resp: # nosemgrep
if resp.status == 200:
return json.loads(resp.read().decode())
except (URLError, OSError, json.JSONDecodeError, ValueError):
Expand Down
Loading