If you run Python code in GitHub Actions, you have two things to secure:
- the Python code in the repository
- the workflow that executes it
Most teams only think about the first one.
GitHub's current security guidance is clear on the second: pin actions immutably, restrict GITHUB_TOKEN permissions, and prefer OIDC over long-lived cloud secrets. That matters for Python teams because your CI usually has access to:
- repository contents
- secrets
- deployment credentials
- package publish permissions
If the workflow is weak, the code scan is not enough.
The short version
For a Python repo, the minimum strong baseline is:
- pin third-party actions by full-length commit SHA
- set explicit
permissions - use OIDC for cloud auth where possible
- avoid privileged behavior on untrusted PRs
- run Python static analysis on every PR
Step 1: Pin third-party actions by full SHA
GitHub documents full-length commit SHA pinning as the immutable option. Tags like @v4 are convenient, but they are still movable references.
This is the safer pattern:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-python@42375524d7b3f331cfa0bf3d4e21d0d3e1df3630
That looks noisier than:
- uses: actions/checkout@v4
but it gives you immutability.
Practical rule:
- first-party GitHub actions are lower risk than random third-party actions
- third-party actions should be pinned first
- deploy, release, and permission-bearing workflows deserve the strictest pinning
Step 2: Lock down GITHUB_TOKEN permissions
Do not let GitHub pick a broader default than your job actually needs.
For most PR scanning jobs, this is a better baseline:
permissions:
contents: read
pull-requests: write
If the job does not comment on pull requests, you may not even need pull-requests: write.
If the job needs cloud federation via OIDC:
permissions:
contents: read
id-token: write
That is the right mental model: grant permissions per workflow purpose, not once at the repo level and forget about it.
Step 3: Prefer OIDC over long-lived cloud secrets
If your deploy job still uses a static AWS, GCP, or Azure credential stored in GitHub secrets, you are taking on unnecessary risk.
OIDC improves this by letting GitHub mint short-lived credentials for a specific workflow run. That means:
- less secret sprawl
- easier rotation
- tighter trust boundaries
This is especially useful for Python services that deploy frequently from CI.
Step 4: Keep untrusted PRs away from privileged workflows
This is where many teams accidentally create dangerous paths.
Rules of thumb:
- do not mix public PR execution with deploy credentials
- treat
pull_request_targetvery carefully - do not check out and execute untrusted code in a privileged context
If you need comments, labels, or metadata handling on PRs, split that from the workflow that has real credentials.
Step 5: Scan Python code on every PR
Workflow hardening is not enough. You still need to scan the Python code itself.
That means checking for:
- hardcoded secrets
- risky shell or subprocess usage
- SQL injection patterns
- weak crypto or unsafe deserialization
- dead code that expands your attack surface
- AI-generated code that looks plausible but is wrong
The easiest place to do that is the pull-request workflow:
name: python-security
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Skylos
run: pip install skylos
- name: Scan Python code
run: skylos . --danger --secrets --quality --gate
If you want the faster "set up the workflow for me" path, use the existing guide here:
Local first, then harden CI
Secure workflows are stronger when you validate the Python repo locally before you automate the same checks in GitHub Actions.
Use one local scan to confirm the project is worth gating in CI:
pip install skylos
skylos . -a
Then use the workflow hardening in this guide and the PR scanning setup in:
- Python Security Scanner for GitHub Actions
- Bandit vs Skylos for Python Security Scanning
- Best Python SAST Tools in 2026
A safer workflow layout for Python teams
One pattern works well for most teams:
Workflow A: Unprivileged PR scan
Purpose:
- test
- lint
- type check
- scan code
- annotate or comment if needed
Permissions:
contents: read- maybe
pull-requests: write
No deploy secrets.
Workflow B: Trusted deploy
Purpose:
- build and deploy from trusted branches only
Permissions:
- minimal deploy permissions
- OIDC if available
No execution of untrusted PR code.
That split removes a lot of accidental risk.
Where Skylos fits
Skylos is not the replacement for GitHub's workflow hardening guidance. It complements it.
Use GitHub's security controls to secure the workflow, and use Skylos to secure the Python code moving through that workflow.
That combination is stronger than either one alone:
- GitHub hardening reduces CI supply-chain and credential risk
- Skylos reduces application and code-review risk
Recommended baseline for a Python repo
If you want the practical version:
- pin third-party actions by SHA
- add explicit
permissions - move cloud auth to OIDC
- keep privileged jobs separate from untrusted PR execution
- scan every PR for Python security and dead code
That gets you most of the real-world value quickly.