Skip to content
Open
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
14 changes: 14 additions & 0 deletions .github/instructions/snyk_rules.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
alwaysApply: true
always_on: true
trigger: always_on
applyTo: "**"
description: Snyk Security At Inception
---

# Project security best practices

- Always run snyk_code_scan tool for new first party code that is generated in a Snyk-supported language.
- If any security issues are found based on newly introduced or modified code or dependencies, attempt to fix the issues using the results context from Snyk.
- Rescan the code after fixing the issues to ensure that the issues were fixed and that there are no newly introduced issues.
- Repeat this process until no new issues are found.
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
security:
name: Security audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Upgrade pip and install pip-audit
run: python -m pip install --upgrade pip pip-audit
- name: Run pip-audit
run: python -m pip_audit -r requirements.txt -f json
- name: Generate CycloneDX SBOM
run: |
python tools/generate_cyclonedx_sbom.py
- name: Upload SBOM artifacts
uses: actions/upload-artifact@v4
with:
name: sbom
path: |
sbom/pip-sbom.json
sbom/cyclonedx-bom.json

test:
name: Run tests
runs-on: ubuntu-latest
needs: security
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install runtime deps for tests
run: python -m pip install --upgrade pip Pillow pytest
- name: Run pytest
run: pytest -q

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install linter
run: python -m pip install --upgrade pip flake8
- name: Run flake8 (non-failing)
run: flake8 --config .flake8 . --exit-zero
37 changes: 37 additions & 0 deletions requirements-pinned.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
boolean.py==5.0
CacheControl==0.14.4
certifi==2026.1.4
charset-normalizer==3.4.4
colorama==0.4.6
cyclonedx-python-lib==11.6.0
defusedxml==0.7.1
filelock==3.21.2
flake8==7.3.0
idna==3.11
iniconfig==2.3.0
license-expression==30.4.4
markdown-it-py==4.0.0
mccabe==0.7.0
mdurl==0.1.2
msgpack==1.1.2
packageurl-python==0.17.6
packaging==26.0
pillow==12.1.1
pip-api==0.0.34
pip-requirements-parser==32.0.1
pip_audit==2.10.0
platformdirs==4.7.1
pluggy==1.6.0
py-serializable==2.1.0
pycodestyle==2.14.0
pyflakes==3.4.0
Pygments==2.19.2
pyparsing==3.3.2
pytest==9.0.2
requests==2.32.5
rich==14.3.2
sortedcontainers==2.4.0
tomli==2.4.0
tomli_w==1.2.0
typing_extensions==4.15.0
urllib3==2.6.3
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
RPi.GPIO==0.7.1
spidev==3.5
Pillow==9.4.0
Pillow>=10.3.0
numpy==2.1.3
rich==13.9.4
pandas==2.2.3
Expand Down
1 change: 1 addition & 0 deletions sbom/pip-sbom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"name": "boolean.py", "version": "5.0"}, {"name": "CacheControl", "version": "0.14.4"}, {"name": "certifi", "version": "2026.1.4"}, {"name": "charset-normalizer", "version": "3.4.4"}, {"name": "colorama", "version": "0.4.6"}, {"name": "cyclonedx-python-lib", "version": "11.6.0"}, {"name": "defusedxml", "version": "0.7.1"}, {"name": "filelock", "version": "3.21.2"}, {"name": "flake8", "version": "7.3.0"}, {"name": "idna", "version": "3.11"}, {"name": "iniconfig", "version": "2.3.0"}, {"name": "license-expression", "version": "30.4.4"}, {"name": "markdown-it-py", "version": "4.0.0"}, {"name": "mccabe", "version": "0.7.0"}, {"name": "mdurl", "version": "0.1.2"}, {"name": "msgpack", "version": "1.1.2"}, {"name": "packageurl-python", "version": "0.17.6"}, {"name": "packaging", "version": "26.0"}, {"name": "pillow", "version": "12.1.1"}, {"name": "pip", "version": "26.0.1"}, {"name": "pip-api", "version": "0.0.34"}, {"name": "pip_audit", "version": "2.10.0"}, {"name": "pip-requirements-parser", "version": "32.0.1"}, {"name": "platformdirs", "version": "4.7.1"}, {"name": "pluggy", "version": "1.6.0"}, {"name": "py-serializable", "version": "2.1.0"}, {"name": "pycodestyle", "version": "2.14.0"}, {"name": "pyflakes", "version": "3.4.0"}, {"name": "Pygments", "version": "2.19.2"}, {"name": "pyparsing", "version": "3.3.2"}, {"name": "pytest", "version": "9.0.2"}, {"name": "requests", "version": "2.32.5"}, {"name": "rich", "version": "14.3.2"}, {"name": "sortedcontainers", "version": "2.4.0"}, {"name": "tomli", "version": "2.4.0"}, {"name": "tomli_w", "version": "1.2.0"}, {"name": "typing_extensions", "version": "4.15.0"}, {"name": "urllib3", "version": "2.6.3"}]
53 changes: 53 additions & 0 deletions tools/generate_cyclonedx_sbom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Generate a minimal CycloneDX SBOM (JSON) from pip list JSON (sbom/pip-sbom.json).

Writes output to `sbom/cyclonedx-bom.json`.
"""
import json
import os
from datetime import datetime


def main():
sbom_dir = os.path.join(os.path.dirname(__file__), '..', 'sbom')
pip_sbom_path = os.path.join(sbom_dir, 'pip-sbom.json')
out_path = os.path.join(sbom_dir, 'cyclonedx-bom.json')

if not os.path.exists(pip_sbom_path):
print(f"Input SBOM not found: {pip_sbom_path}")
return 1

with open(pip_sbom_path, 'r', encoding='utf-8') as f:
pkgs = json.load(f)

components = []
for p in pkgs:
comp = {
"type": "library",
"name": p.get('name'),
"version": p.get('version')
}
components.append(comp)

bom = {
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": f"urn:uuid:{datetime.utcnow().isoformat()}",
"version": 1,
"metadata": {
"timestamp": datetime.utcnow().isoformat() + "Z",
"tools": [{"vendor": "local", "name": "generate_cyclonedx_sbom.py", "version": "1"}]
},
"components": components,
}

os.makedirs(sbom_dir, exist_ok=True)
with open(out_path, 'w', encoding='utf-8') as f:
json.dump(bom, f, indent=2, ensure_ascii=False)

print(f"Wrote CycloneDX SBOM to {out_path}")
return 0


if __name__ == '__main__':
raise SystemExit(main())