Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1da2cb7
ci(security): add typed redacted evidence and exception primitives
yeongseon Oct 22, 2026
0626f2f
ci(security): enforce fail-closed runtime advisory policy
yeongseon Oct 22, 2026
12177d5
test(security): verify pinned browser integrity offline and live
yeongseon Oct 22, 2026
199a0f3
ci(security): capture complete executable-surface scan evidence
yeongseon Oct 22, 2026
e07a62c
ci(security): retain scan artifacts and type-check security tools
yeongseon Oct 22, 2026
13073eb
ci(security): gate candidate publication on runtime audit and SRI
yeongseon Oct 22, 2026
927fcf8
docs(security): define blocking policy and evidence recovery
yeongseon Oct 22, 2026
0e178ab
docs(security): distinguish local examples from production defaults
yeongseon Oct 22, 2026
8b38102
test: type optional validation decorators without stale suppressions
yeongseon Oct 22, 2026
db2329a
ci(security): verify candidate identity and freeze third-party inventory
yeongseon Oct 23, 2026
8872321
ci(security): audit unpublished candidates through verified dependenc…
yeongseon Oct 23, 2026
7914927
ci(security): classify action-only semgrep evidence as incomplete
yeongseon Oct 23, 2026
38a1a31
ci(security): share make entry points and always collect evidence
yeongseon Oct 23, 2026
e09c0df
docs(security): explain candidate inventory and action-only evidence
yeongseon Oct 23, 2026
9350af8
style(scripts): sort screenshot checker imports
yeongseon Oct 23, 2026
b8156c3
Merge branch 'main' into ci/security-gates-evidence
yeongseon Oct 24, 2026
a53df03
fix(ci): quiet Bandit so evidence JSON parses above 50 files
yeongseon Oct 24, 2026
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
65 changes: 30 additions & 35 deletions .github/workflows/dependency-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,44 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python 3.14
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"

- name: Install pip-audit and runtime dependencies
- name: Build wheel and isolated reporting environments
run: |
python -m pip install --upgrade pip
python -m pip install 'pip-audit==2.10.1' .

- name: Audit runtime dependencies
python -m pip install 'pip-audit==2.10.1' build
python -m build --wheel
python -m venv --without-pip "$RUNNER_TEMP/runtime"
python -m pip --python "$RUNNER_TEMP/runtime/bin/python" install dist/*.whl
python -m venv --without-pip "$RUNNER_TEMP/development"
python -m pip --python "$RUNNER_TEMP/development/bin/python" install '.[dev]'
- name: Report runtime dependency policy
id: runtime
continue-on-error: true
run: |
set +e
python -m pip_audit --format markdown > runtime-audit.md
status=$?
set -e
cat runtime-audit.md >> "$GITHUB_STEP_SUMMARY"
exit "$status"

- name: Install development dependencies
run: python -m pip install '.[dev]'

- name: Audit runtime and development dependencies
id: development
site_packages=$("$RUNNER_TEMP/runtime/bin/python" -c 'import sysconfig; print(sysconfig.get_path("purelib"))')
wheels=(dist/*.whl)
test "${#wheels[@]}" -eq 1
make security-runtime SECURITY_PYTHON=python SITE_PACKAGES="$site_packages" CANDIDATE_WHEEL="${wheels[0]}" SECURITY_OUTPUT=security-results/runtime
- name: Report development dependencies with the same classifier
if: always()
continue-on-error: true
run: |
set +e
python -m pip_audit --format markdown > development-audit.md
status=$?
set -e
cat development-audit.md >> "$GITHUB_STEP_SUMMARY"
exit "$status"

- name: Summarize audit outcomes
env:
RUNTIME_OUTCOME: ${{ steps.runtime.outcome }}
DEVELOPMENT_OUTCOME: ${{ steps.development.outcome }}
site_packages=$("$RUNNER_TEMP/development/bin/python" -c 'import sysconfig; print(sysconfig.get_path("purelib"))')
wheels=(dist/*.whl)
test "${#wheels[@]}" -eq 1
make security-development SECURITY_PYTHON=python SITE_PACKAGES="$site_packages" CANDIDATE_WHEEL="${wheels[0]}" SECURITY_OUTPUT=security-results/development
- name: Verify pinned CDN integrity
if: always()
continue-on-error: true
run: |
{
echo "## Dependency audit outcomes"
echo "- Runtime dependencies: ${RUNTIME_OUTCOME}"
echo "- Runtime + development dependencies: ${DEVELOPMENT_OUTCOME}"
} >> "$GITHUB_STEP_SUMMARY"
site_packages=$("$RUNNER_TEMP/runtime/bin/python" -c 'import sysconfig; print(sysconfig.get_path("purelib"))')
PYTHONPATH="$site_packages" make security-browser SECURITY_PYTHON="$RUNNER_TEMP/runtime/bin/python" SECURITY_OUTPUT=security-results/browser
- name: Upload redacted reporting evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: dependency-security-report
path: security-results/**
if-no-files-found: error
51 changes: 50 additions & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,58 @@ jobs:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}


runtime-dependency-audit:
name: Runtime dependency and browser integrity gate
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
interpreter: ["3.11", "3.12", "3.13", "3.14"]
steps:
- name: Checkout security policy and tooling
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up audit interpreter
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.interpreter }}
- name: Download the same candidate wheel
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: dist
path: candidate-dist
- name: Install only the candidate runtime closure in an isolated environment
run: |
python -m pip install 'pip-audit==2.10.1'
python -m venv --without-pip "$RUNNER_TEMP/candidate"
wheels=(candidate-dist/*.whl)
test "${#wheels[@]}" -eq 1
python -m pip --python "$RUNNER_TEMP/candidate/bin/python" install "${wheels[0]}"
- name: Enforce runtime advisory policy
if: always()
run: |
site_packages=$("$RUNNER_TEMP/candidate/bin/python" -c 'import sysconfig; print(sysconfig.get_path("purelib"))')
wheels=(candidate-dist/*.whl)
test "${#wheels[@]}" -eq 1
make security-runtime SECURITY_PYTHON=python SITE_PACKAGES="$site_packages" CANDIDATE_WHEEL="${wheels[0]}" SECURITY_OUTPUT=security-results/runtime
- name: Verify browser CDN integrity from the candidate wheel
if: always()
run: |
site_packages=$("$RUNNER_TEMP/candidate/bin/python" -c 'import sysconfig; print(sysconfig.get_path("purelib"))')
PYTHONPATH="$site_packages" make security-browser SECURITY_PYTHON="$RUNNER_TEMP/candidate/bin/python" SECURITY_OUTPUT=security-results/browser
- name: Upload redacted release security evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-security-${{ matrix.interpreter }}
path: security-results/**
if-no-files-found: error

publish:
name: Publish to PyPI
needs: [build, lib-tests, cookbook-smoke, cookbook-host-smoke, azure-e2e]
needs: [build, lib-tests, cookbook-smoke, cookbook-host-smoke, azure-e2e, runtime-dependency-audit]
runs-on: ubuntu-latest
environment: pypi
permissions:
Expand Down
44 changes: 38 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
branches: [main]
schedule:
- cron: "0 2 * * 1"
workflow_dispatch:

permissions:
contents: read
Expand All @@ -23,13 +24,23 @@ jobs:
with:
python-version: "3.11"

- name: Install Hatch and dependencies via Makefile
run: |
pip install hatch
make install
- name: Install scanner dependencies
run: pip install -e ".[dev]"

- name: Run Bandit
run: make security
- name: Run Bandit on declared executable surfaces with fail-closed evidence
if: always()
run: make security-bandit SECURITY_PYTHON=python

- name: Type check security tooling
run: make security-types SECURITY_PYTHON=python

- name: Upload redacted security evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: security-bandit
path: security-results/**
if-no-files-found: error

semgrep:
runs-on: ubuntu-latest
Expand All @@ -38,6 +49,27 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Run Semgrep
id: scan
uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1
with:
config: p/python

- name: Set up evidence interpreter
if: always()
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"

- name: Record Semgrep execution evidence
if: always()
env:
SCAN_OUTCOME: ${{ steps.scan.outcome }}
run: make security-semgrep SECURITY_PYTHON=python

- name: Upload redacted security evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: security-semgrep
path: security-results/**
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ htmlcov/
# Build artifacts
build/
dist/
security-results/
*.egg-info/

# IDE
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ Every tier runs inside `publish-pypi.yml` on the tag, and **all of them gate the
| `cookbook-smoke` | downstream import/registration drift |
| `cookbook-host-smoke` | candidate wheel installs cleanly and a real `func` host + Azurite boots with it present, no cloud. NOTE: the cookbook HTTP examples do not import this package, so this is a host-boot smoke, **not** proof of this package's own runtime behavior (tracked separately) |
| `azure-e2e` | cloud-only drift — deploys to real Azure, runs the live e2e suite, uploads an `azure-cert` record |
| `runtime-dependency-audit` | blocks unexcepted runtime advisories and CDN/SRI failures on the candidate wheel; see [SECURITY.md](SECURITY.md) for evidence, expiry and tool-error policy |
| `publish` | uploads the exact artifact `build` produced; it never rebuilds |

### Recovery
Expand All @@ -291,6 +292,10 @@ Every tier runs inside `publish-pypi.yml` on the tag, and **all of them gate the

## Golden Commands

Security entry points and candidate-inventory verification are documented in
[SECURITY.md](SECURITY.md#automated-security-controls). `make security` includes the
expanded executable-surface inventory scan used in CI.

Use Makefile entry points only. Do not bypass the Makefile in CI or contributor guidance.

| Purpose | Command |
Expand Down
30 changes: 28 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,34 @@ lint: ensure-hatch
@$(HATCH) run lint

.PHONY: security
security: ensure-hatch
@$(HATCH) run security
security: security-bandit

# Shared security entry points; SECURITY_PYTHON selects an already provisioned interpreter.
SECURITY_PYTHON ?= hatch run python
SECURITY_OUTPUT ?= security-results
SITE_PACKAGES ?=
CANDIDATE_WHEEL ?=
SCAN_OUTCOME ?=
SECURITY_TOOLS := tools/security_report.py tools/security_exceptions.py tools/security_evidence.py tools/dependency_audit_policy.py tools/candidate_inventory.py tools/browser_integrity.py

.PHONY: security-bandit security-semgrep security-types security-runtime security-development security-browser
security-bandit:
@$(SECURITY_PYTHON) -m tools.security_evidence bandit --output "$(SECURITY_OUTPUT)"

security-semgrep:
@$(SECURITY_PYTHON) -m tools.security_evidence semgrep --outcome "$(SCAN_OUTCOME)" --output "$(SECURITY_OUTPUT)"; code=$$?; test $$code -eq 0 -o $$code -eq 3

security-types:
@$(SECURITY_PYTHON) -m mypy --explicit-package-bases $(SECURITY_TOOLS)

security-runtime:
@$(SECURITY_PYTHON) -m tools.dependency_audit_policy --site-packages "$(SITE_PACKAGES)" --candidate-wheel "$(CANDIDATE_WHEEL)" --output "$(SECURITY_OUTPUT)"

security-development:
@$(SECURITY_PYTHON) -m tools.dependency_audit_policy --site-packages "$(SITE_PACKAGES)" --candidate-wheel "$(CANDIDATE_WHEEL)" --development --output "$(SECURITY_OUTPUT)"

security-browser:
@$(SECURITY_PYTHON) -m tools.browser_integrity --output "$(SECURITY_OUTPUT)"

.PHONY: check
check: ensure-hatch
Expand Down
Loading
Loading