saferun-cli 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- saferun_cli-0.0.2/.github/workflows/ci.yml +224 -0
- saferun_cli-0.0.2/.github/workflows/release-analyzer.yml +208 -0
- saferun_cli-0.0.2/IMPLEMENTATION.md +463 -0
- saferun_cli-0.0.2/LICENSE +21 -0
- saferun_cli-0.0.2/MANIFEST.in +11 -0
- saferun_cli-0.0.2/PKG-INFO +136 -0
- saferun_cli-0.0.2/PROJECT.md +267 -0
- saferun_cli-0.0.2/README.md +104 -0
- saferun_cli-0.0.2/SECURITY.md +21 -0
- saferun_cli-0.0.2/docker/analyzer/Dockerfile +43 -0
- saferun_cli-0.0.2/docker/analyzer/README.md +20 -0
- saferun_cli-0.0.2/docker/analyzer/THIRD_PARTY_NOTICES.md +125 -0
- saferun_cli-0.0.2/docker/analyzer/build.py +62 -0
- saferun_cli-0.0.2/docker/analyzer/build.sh +5 -0
- saferun_cli-0.0.2/docker/analyzer/fetch_verified.py +64 -0
- saferun_cli-0.0.2/docker/analyzer/licenses/osv-scanner-v2.7.0-LICENSE +202 -0
- saferun_cli-0.0.2/docker/analyzer/licenses/osv-scanner-v2.7.0-go-module-licenses.txt +19967 -0
- saferun_cli-0.0.2/docker/analyzer/licenses/osv-scanner-v2.7.0-go-modules.md +199 -0
- saferun_cli-0.0.2/docker/analyzer/licenses/semgrep-LICENSE +504 -0
- saferun_cli-0.0.2/docker/analyzer/semgrep-requirements.txt +69 -0
- saferun_cli-0.0.2/docs/cli.md +142 -0
- saferun_cli-0.0.2/docs/engine.md +30 -0
- saferun_cli-0.0.2/docs/install-and-test.md +72 -0
- saferun_cli-0.0.2/docs/platform-test-checklist.md +14 -0
- saferun_cli-0.0.2/docs/platforms.md +90 -0
- saferun_cli-0.0.2/docs/release-checklist-analyzer-v1.0.1.md +104 -0
- saferun_cli-0.0.2/docs/release-images.md +104 -0
- saferun_cli-0.0.2/docs/release-notes-analyzer-v1.0.1.md +36 -0
- saferun_cli-0.0.2/docs/release-pypi.md +57 -0
- saferun_cli-0.0.2/docs/sandbox.md +24 -0
- saferun_cli-0.0.2/pyproject.toml +56 -0
- saferun_cli-0.0.2/saferun/__init__.py +3 -0
- saferun_cli-0.0.2/saferun/__main__.py +5 -0
- saferun_cli-0.0.2/saferun/cli.py +533 -0
- saferun_cli-0.0.2/saferun/dockerdiag.py +65 -0
- saferun_cli-0.0.2/saferun/doctor.py +159 -0
- saferun_cli-0.0.2/saferun/engine/__init__.py +7 -0
- saferun_cli-0.0.2/saferun/engine/analyzer.py +30 -0
- saferun_cli-0.0.2/saferun/engine/context.py +27 -0
- saferun_cli-0.0.2/saferun/engine/engine.py +100 -0
- saferun_cli-0.0.2/saferun/engine/model.py +146 -0
- saferun_cli-0.0.2/saferun/engine/node.py +145 -0
- saferun_cli-0.0.2/saferun/engine/osv.py +291 -0
- saferun_cli-0.0.2/saferun/engine/osvdb.py +367 -0
- saferun_cli-0.0.2/saferun/engine/paths.py +41 -0
- saferun_cli-0.0.2/saferun/engine/pysyntax.py +135 -0
- saferun_cli-0.0.2/saferun/engine/rules/python.yml +103 -0
- saferun_cli-0.0.2/saferun/engine/semgrep.py +173 -0
- saferun_cli-0.0.2/saferun/engine/tools.py +167 -0
- saferun_cli-0.0.2/saferun/engine/verdict.py +47 -0
- saferun_cli-0.0.2/saferun/hostos/__init__.py +188 -0
- saferun_cli-0.0.2/saferun/hostos/_win.py +316 -0
- saferun_cli-0.0.2/saferun/hostos/acl.py +172 -0
- saferun_cli-0.0.2/saferun/hostos/fsops.py +41 -0
- saferun_cli-0.0.2/saferun/hostos/process.py +82 -0
- saferun_cli-0.0.2/saferun/hostos/winfs.py +164 -0
- saferun_cli-0.0.2/saferun/runcmd.py +139 -0
- saferun_cli-0.0.2/saferun/sandbox/__init__.py +11 -0
- saferun_cli-0.0.2/saferun/sandbox/analyzer.py +192 -0
- saferun_cli-0.0.2/saferun/sandbox/attestation.py +110 -0
- saferun_cli-0.0.2/saferun/sandbox/config.py +121 -0
- saferun_cli-0.0.2/saferun/sandbox/controls.py +104 -0
- saferun_cli-0.0.2/saferun/sandbox/copy.py +338 -0
- saferun_cli-0.0.2/saferun/sandbox/copy_windows.py +281 -0
- saferun_cli-0.0.2/saferun/sandbox/cosign_tool.py +134 -0
- saferun_cli-0.0.2/saferun/sandbox/docker.py +167 -0
- saferun_cli-0.0.2/saferun/sandbox/images.py +110 -0
- saferun_cli-0.0.2/saferun/sandbox/profiles.py +40 -0
- saferun_cli-0.0.2/saferun/sandbox/reaper.py +127 -0
- saferun_cli-0.0.2/saferun/sandbox/runner.py +268 -0
- saferun_cli-0.0.2/saferun/sandbox/secrets.py +91 -0
- saferun_cli-0.0.2/saferun/sandbox/signature.py +117 -0
- saferun_cli-0.0.2/saferun/sandbox/verify.py +75 -0
- saferun_cli-0.0.2/saferun/sandbox/workspace.py +92 -0
- saferun_cli-0.0.2/saferun/setup.py +559 -0
- saferun_cli-0.0.2/saferun/ui.py +133 -0
- saferun_cli-0.0.2/saferun_cli.egg-info/PKG-INFO +136 -0
- saferun_cli-0.0.2/saferun_cli.egg-info/SOURCES.txt +190 -0
- saferun_cli-0.0.2/saferun_cli.egg-info/dependency_links.txt +1 -0
- saferun_cli-0.0.2/saferun_cli.egg-info/entry_points.txt +2 -0
- saferun_cli-0.0.2/saferun_cli.egg-info/requires.txt +6 -0
- saferun_cli-0.0.2/saferun_cli.egg-info/top_level.txt +1 -0
- saferun_cli-0.0.2/setup.cfg +4 -0
- saferun_cli-0.0.2/tests/conftest.py +106 -0
- saferun_cli-0.0.2/tests/engine_helpers.py +134 -0
- saferun_cli-0.0.2/tests/posix_fsops.py +85 -0
- saferun_cli-0.0.2/tests/test_adversarial.py +410 -0
- saferun_cli-0.0.2/tests/test_analyzer_isolation.py +409 -0
- saferun_cli-0.0.2/tests/test_analyzer_lock.py +168 -0
- saferun_cli-0.0.2/tests/test_ci_workflow.py +56 -0
- saferun_cli-0.0.2/tests/test_cleanup.py +184 -0
- saferun_cli-0.0.2/tests/test_cleanup_hardening.py +137 -0
- saferun_cli-0.0.2/tests/test_cli.py +191 -0
- saferun_cli-0.0.2/tests/test_copy_windows_engine.py +164 -0
- saferun_cli-0.0.2/tests/test_engine_integration.py +176 -0
- saferun_cli-0.0.2/tests/test_engine_node.py +155 -0
- saferun_cli-0.0.2/tests/test_engine_osv.py +257 -0
- saferun_cli-0.0.2/tests/test_engine_pysyntax.py +111 -0
- saferun_cli-0.0.2/tests/test_engine_real_tools.py +131 -0
- saferun_cli-0.0.2/tests/test_engine_semgrep.py +157 -0
- saferun_cli-0.0.2/tests/test_engine_tools.py +83 -0
- saferun_cli-0.0.2/tests/test_engine_verdict.py +69 -0
- saferun_cli-0.0.2/tests/test_hardening.py +496 -0
- saferun_cli-0.0.2/tests/test_hardlinks.py +139 -0
- saferun_cli-0.0.2/tests/test_hostos_acl.py +167 -0
- saferun_cli-0.0.2/tests/test_hostos_platform.py +68 -0
- saferun_cli-0.0.2/tests/test_image_pins.py +317 -0
- saferun_cli-0.0.2/tests/test_license.py +26 -0
- saferun_cli-0.0.2/tests/test_macos_platform.py +34 -0
- saferun_cli-0.0.2/tests/test_node_profile.py +118 -0
- saferun_cli-0.0.2/tests/test_osv_db_integrity.py +412 -0
- saferun_cli-0.0.2/tests/test_races.py +441 -0
- saferun_cli-0.0.2/tests/test_release_signature.py +364 -0
- saferun_cli-0.0.2/tests/test_report_fixture.py +68 -0
- saferun_cli-0.0.2/tests/test_require_docker.py +28 -0
- saferun_cli-0.0.2/tests/test_sandbox.py +242 -0
- saferun_cli-0.0.2/tests/test_sensitive.py +202 -0
- saferun_cli-0.0.2/tests/test_setup.py +675 -0
- saferun_cli-0.0.2/tests/test_stage2_install.py +580 -0
- saferun_cli-0.0.2/tests/test_ui.py +321 -0
- saferun_cli-0.0.2/tests/test_windows_native.py +613 -0
- saferun_cli-0.0.2/tests/test_workspace.py +155 -0
- saferun_cli-0.0.2/web/.node-version +1 -0
- saferun_cli-0.0.2/web/index.html +14 -0
- saferun_cli-0.0.2/web/package-lock.json +1864 -0
- saferun_cli-0.0.2/web/package.json +25 -0
- saferun_cli-0.0.2/web/src/App.jsx +16 -0
- saferun_cli-0.0.2/web/src/components/CheckCoverage/CheckCoverage.jsx +36 -0
- saferun_cli-0.0.2/web/src/components/CommandBlock/CommandBlock.css +5 -0
- saferun_cli-0.0.2/web/src/components/CommandBlock/CommandBlock.jsx +30 -0
- saferun_cli-0.0.2/web/src/components/Emblem/Emblem.css +27 -0
- saferun_cli-0.0.2/web/src/components/Emblem/Emblem.jsx +45 -0
- saferun_cli-0.0.2/web/src/components/FindingsTable/FindingsTable.css +12 -0
- saferun_cli-0.0.2/web/src/components/FindingsTable/FindingsTable.jsx +77 -0
- saferun_cli-0.0.2/web/src/components/GapsPanel/GapsPanel.css +1 -0
- saferun_cli-0.0.2/web/src/components/GapsPanel/GapsPanel.jsx +14 -0
- saferun_cli-0.0.2/web/src/components/Mark/Mark.jsx +9 -0
- saferun_cli-0.0.2/web/src/components/ReportView/ReportView.jsx +17 -0
- saferun_cli-0.0.2/web/src/components/ReportView/ReportView.test.jsx +110 -0
- saferun_cli-0.0.2/web/src/components/Section/Section.css +22 -0
- saferun_cli-0.0.2/web/src/components/Section/Section.jsx +30 -0
- saferun_cli-0.0.2/web/src/components/SeverityTally/SeverityTally.css +1 -0
- saferun_cli-0.0.2/web/src/components/SeverityTally/SeverityTally.jsx +13 -0
- saferun_cli-0.0.2/web/src/components/SiteFooter/SiteFooter.css +4 -0
- saferun_cli-0.0.2/web/src/components/SiteFooter/SiteFooter.jsx +26 -0
- saferun_cli-0.0.2/web/src/components/SiteNav/SiteNav.css +19 -0
- saferun_cli-0.0.2/web/src/components/SiteNav/SiteNav.jsx +28 -0
- saferun_cli-0.0.2/web/src/components/StatusBadge/StatusBadge.css +3 -0
- saferun_cli-0.0.2/web/src/components/StatusBadge/StatusBadge.jsx +8 -0
- saferun_cli-0.0.2/web/src/components/UpgradePlan/UpgradePlan.css +1 -0
- saferun_cli-0.0.2/web/src/components/UpgradePlan/UpgradePlan.jsx +30 -0
- saferun_cli-0.0.2/web/src/components/VerdictCard/VerdictCard.css +11 -0
- saferun_cli-0.0.2/web/src/components/VerdictCard/VerdictCard.jsx +42 -0
- saferun_cli-0.0.2/web/src/components/landing/CheckTicker/CheckTicker.css +10 -0
- saferun_cli-0.0.2/web/src/components/landing/CheckTicker/CheckTicker.jsx +18 -0
- saferun_cli-0.0.2/web/src/components/landing/Faq/Faq.css +5 -0
- saferun_cli-0.0.2/web/src/components/landing/Faq/Faq.jsx +26 -0
- saferun_cli-0.0.2/web/src/components/landing/Features/Features.css +5 -0
- saferun_cli-0.0.2/web/src/components/landing/Features/Features.jsx +20 -0
- saferun_cli-0.0.2/web/src/components/landing/FlowDiagram/FlowDiagram.css +23 -0
- saferun_cli-0.0.2/web/src/components/landing/FlowDiagram/FlowDiagram.jsx +33 -0
- saferun_cli-0.0.2/web/src/components/landing/GithubCta/GithubCta.css +4 -0
- saferun_cli-0.0.2/web/src/components/landing/GithubCta/GithubCta.jsx +21 -0
- saferun_cli-0.0.2/web/src/components/landing/HeroProof/HeroProof.css +9 -0
- saferun_cli-0.0.2/web/src/components/landing/HeroProof/HeroProof.jsx +23 -0
- saferun_cli-0.0.2/web/src/components/landing/HowItWorks/HowItWorks.css +7 -0
- saferun_cli-0.0.2/web/src/components/landing/HowItWorks/HowItWorks.jsx +20 -0
- saferun_cli-0.0.2/web/src/components/landing/ProblemSolution/ProblemSolution.css +10 -0
- saferun_cli-0.0.2/web/src/components/landing/ProblemSolution/ProblemSolution.jsx +28 -0
- saferun_cli-0.0.2/web/src/components/landing/QuickStart/QuickStart.css +10 -0
- saferun_cli-0.0.2/web/src/components/landing/QuickStart/QuickStart.jsx +20 -0
- saferun_cli-0.0.2/web/src/components/landing/ReportDemo/ReportDemo.css +13 -0
- saferun_cli-0.0.2/web/src/components/landing/ReportDemo/ReportDemo.jsx +41 -0
- saferun_cli-0.0.2/web/src/components/landing/ScrollHero/ScrollHero.css +80 -0
- saferun_cli-0.0.2/web/src/components/landing/ScrollHero/ScrollHero.jsx +103 -0
- saferun_cli-0.0.2/web/src/components/landing/SecuritySection/SecuritySection.css +8 -0
- saferun_cli-0.0.2/web/src/components/landing/SecuritySection/SecuritySection.jsx +20 -0
- saferun_cli-0.0.2/web/src/components/landing/TerminalDemo/TerminalDemo.css +29 -0
- saferun_cli-0.0.2/web/src/components/landing/TerminalDemo/TerminalDemo.jsx +30 -0
- saferun_cli-0.0.2/web/src/content/site.js +53 -0
- saferun_cli-0.0.2/web/src/fixtures/report.json +264 -0
- saferun_cli-0.0.2/web/src/index.css +46 -0
- saferun_cli-0.0.2/web/src/main.jsx +6 -0
- saferun_cli-0.0.2/web/src/pages/GuidePage/GuidePage.css +115 -0
- saferun_cli-0.0.2/web/src/pages/GuidePage/GuidePage.jsx +392 -0
- saferun_cli-0.0.2/web/src/pages/GuidePage/GuidePage.test.jsx +141 -0
- saferun_cli-0.0.2/web/src/pages/LandingPage/LandingPage.jsx +34 -0
- saferun_cli-0.0.2/web/src/pages/LandingPage/LandingPage.test.jsx +174 -0
- saferun_cli-0.0.2/web/src/test-setup.js +4 -0
- saferun_cli-0.0.2/web/src/types/index.js +21 -0
- saferun_cli-0.0.2/web/src/utils/report.js +97 -0
- saferun_cli-0.0.2/web/vite.config.js +10 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Continuous integration: tests and checks only. This workflow never publishes anything, never logs in to a registry, has no secrets and only
|
|
2
|
+
# read access to the repository. Publishing the analyzer image is a separate, tag-triggered workflow (release-analyzer.yml).
|
|
3
|
+
#
|
|
4
|
+
# Jobs marked "advisory" use continue-on-error: they report problems without blocking a merge, because they depend on the runner's Docker
|
|
5
|
+
# daemon or on third-party advisory databases. Every action is pinned to a full commit SHA.
|
|
6
|
+
name: ci
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
pull_request:
|
|
10
|
+
push:
|
|
11
|
+
branches:
|
|
12
|
+
- main
|
|
13
|
+
- 'feature/**'
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
python:
|
|
24
|
+
name: Python tests (Docker tests excluded)
|
|
25
|
+
runs-on: ubuntu-24.04
|
|
26
|
+
timeout-minutes: 25
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
29
|
+
with:
|
|
30
|
+
persist-credentials: false
|
|
31
|
+
|
|
32
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.12"
|
|
35
|
+
cache: pip
|
|
36
|
+
cache-dependency-path: pyproject.toml
|
|
37
|
+
|
|
38
|
+
- name: Install SafeRun with the dev extra
|
|
39
|
+
run: python -m pip install -e '.[dev]'
|
|
40
|
+
|
|
41
|
+
- name: Syntax check of all Python sources and the shell build script
|
|
42
|
+
run: |
|
|
43
|
+
python -m compileall -q saferun tests docker/analyzer/fetch_verified.py docker/analyzer/build.py
|
|
44
|
+
sh -n docker/analyzer/build.sh
|
|
45
|
+
|
|
46
|
+
- name: Run the tests that do not need Docker
|
|
47
|
+
# Docker-dependent tests are covered by the separate (advisory) docker job. They are deselected here, not hidden: see the header line.
|
|
48
|
+
run: python -m pytest -q -ra -m "not docker"
|
|
49
|
+
|
|
50
|
+
platforms:
|
|
51
|
+
name: Python tests on ${{ matrix.os }} (Docker tests excluded)
|
|
52
|
+
# Native macOS and Windows runs of everything that does not need a Linux Docker daemon. GitHub's macOS and Windows runners cannot run the
|
|
53
|
+
# Linux sandbox containers, so Docker integration is verified on Linux only (the docker job below); see docs/platforms.md.
|
|
54
|
+
# The Intel macOS leg is advisory: GitHub may retire that runner image.
|
|
55
|
+
runs-on: ${{ matrix.os }}
|
|
56
|
+
timeout-minutes: 30
|
|
57
|
+
continue-on-error: ${{ matrix.experimental }}
|
|
58
|
+
strategy:
|
|
59
|
+
fail-fast: false
|
|
60
|
+
matrix:
|
|
61
|
+
include:
|
|
62
|
+
- os: windows-2022
|
|
63
|
+
experimental: false
|
|
64
|
+
- os: macos-15
|
|
65
|
+
experimental: false
|
|
66
|
+
- os: macos-15-intel
|
|
67
|
+
experimental: true
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
70
|
+
with:
|
|
71
|
+
persist-credentials: false
|
|
72
|
+
|
|
73
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
74
|
+
with:
|
|
75
|
+
python-version: "3.12"
|
|
76
|
+
cache: pip
|
|
77
|
+
cache-dependency-path: pyproject.toml
|
|
78
|
+
|
|
79
|
+
- name: Install SafeRun with the dev extra
|
|
80
|
+
run: python -m pip install -e ".[dev]"
|
|
81
|
+
|
|
82
|
+
- name: Platform and Docker diagnostics (informational)
|
|
83
|
+
shell: bash
|
|
84
|
+
run: |
|
|
85
|
+
python -c "import platform, sys; print(platform.platform(), platform.machine(), sys.version)"
|
|
86
|
+
docker version || echo "docker: not usable on this runner"
|
|
87
|
+
docker info --format 'daemon OSType: {{.OSType}}' || echo "docker daemon: not reachable (expected on this runner)"
|
|
88
|
+
|
|
89
|
+
- name: Run the tests that do not need Docker
|
|
90
|
+
run: python -m pytest -q -ra -m "not docker"
|
|
91
|
+
|
|
92
|
+
- name: State what was not verified
|
|
93
|
+
if: always()
|
|
94
|
+
shell: bash
|
|
95
|
+
run: |
|
|
96
|
+
{
|
|
97
|
+
echo "### ${{ matrix.os }}"
|
|
98
|
+
echo "Docker sandbox and analyzer integration tests were NOT run on this platform (no Linux Docker daemon on the runner)."
|
|
99
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
100
|
+
|
|
101
|
+
web:
|
|
102
|
+
name: Website tests and build
|
|
103
|
+
runs-on: ubuntu-24.04
|
|
104
|
+
timeout-minutes: 15
|
|
105
|
+
defaults:
|
|
106
|
+
run:
|
|
107
|
+
working-directory: web
|
|
108
|
+
steps:
|
|
109
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
110
|
+
with:
|
|
111
|
+
persist-credentials: false
|
|
112
|
+
|
|
113
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
114
|
+
with:
|
|
115
|
+
node-version: "22"
|
|
116
|
+
cache: npm
|
|
117
|
+
cache-dependency-path: web/package-lock.json
|
|
118
|
+
|
|
119
|
+
- name: Install from the lockfile without running install scripts
|
|
120
|
+
run: npm ci --ignore-scripts
|
|
121
|
+
|
|
122
|
+
- name: Unit tests
|
|
123
|
+
run: npm test
|
|
124
|
+
|
|
125
|
+
- name: Production build
|
|
126
|
+
run: npm run build
|
|
127
|
+
|
|
128
|
+
package:
|
|
129
|
+
name: Build and inspect the wheel and sdist
|
|
130
|
+
runs-on: ubuntu-24.04
|
|
131
|
+
timeout-minutes: 15
|
|
132
|
+
steps:
|
|
133
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
134
|
+
with:
|
|
135
|
+
persist-credentials: false
|
|
136
|
+
|
|
137
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
138
|
+
with:
|
|
139
|
+
python-version: "3.12"
|
|
140
|
+
|
|
141
|
+
- name: Build
|
|
142
|
+
run: |
|
|
143
|
+
python -m pip install 'build>=1.2' 'twine>=5'
|
|
144
|
+
python -m build
|
|
145
|
+
python -m twine check dist/*
|
|
146
|
+
|
|
147
|
+
- name: Check the contents of both distributions
|
|
148
|
+
run: |
|
|
149
|
+
set -eu
|
|
150
|
+
wheel=$(ls dist/*.whl)
|
|
151
|
+
python -m zipfile -l "$wheel" | grep -q 'saferun/engine/rules/python.yml'
|
|
152
|
+
python -m zipfile -l "$wheel" | grep -q 'saferun/cli.py'
|
|
153
|
+
python -m zipfile -l "$wheel" | grep -q 'saferun/doctor.py'
|
|
154
|
+
for bad in 'docker/' 'tests/' 'web/' 'node_modules' '\.venv'; do
|
|
155
|
+
if python -m zipfile -l "$wheel" | grep -q "$bad"; then echo "wheel contains $bad" >&2; exit 1; fi
|
|
156
|
+
done
|
|
157
|
+
tar tzf dist/*.tar.gz | grep -q 'docker/analyzer/Dockerfile'
|
|
158
|
+
tar tzf dist/*.tar.gz | grep -q 'web/package-lock.json'
|
|
159
|
+
if tar tzf dist/*.tar.gz | grep -q 'node_modules/'; then echo "sdist contains node_modules" >&2; exit 1; fi
|
|
160
|
+
|
|
161
|
+
- name: Install the wheel into a clean virtual environment and run the CLI away from the checkout
|
|
162
|
+
run: |
|
|
163
|
+
set -eu
|
|
164
|
+
python -m venv "$RUNNER_TEMP/clean"
|
|
165
|
+
"$RUNNER_TEMP/clean/bin/pip" install dist/*.whl
|
|
166
|
+
cd "$RUNNER_TEMP"
|
|
167
|
+
"$RUNNER_TEMP/clean/bin/saferun" --help > /dev/null
|
|
168
|
+
"$RUNNER_TEMP/clean/bin/saferun" --version
|
|
169
|
+
# doctor exits 1 on a runner without Docker/analyzer, which is expected; it must still run and print its checks
|
|
170
|
+
"$RUNNER_TEMP/clean/bin/saferun" doctor --format json | python -c "import json,sys; d=json.load(sys.stdin); assert d['checks']"
|
|
171
|
+
"$RUNNER_TEMP/clean/bin/python" -c "import saferun.engine.semgrep as s; assert s.RULES_FILE.is_file(), s.RULES_FILE"
|
|
172
|
+
|
|
173
|
+
audit:
|
|
174
|
+
name: Dependency audit (advisory)
|
|
175
|
+
runs-on: ubuntu-24.04
|
|
176
|
+
timeout-minutes: 15
|
|
177
|
+
continue-on-error: true
|
|
178
|
+
steps:
|
|
179
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
180
|
+
with:
|
|
181
|
+
persist-credentials: false
|
|
182
|
+
|
|
183
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
184
|
+
with:
|
|
185
|
+
node-version: "22"
|
|
186
|
+
|
|
187
|
+
- name: npm audit of the website's production dependencies
|
|
188
|
+
working-directory: web
|
|
189
|
+
run: npm audit --omit=dev --audit-level=high
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
docker:
|
|
193
|
+
name: Sandbox and analyzer tests with Docker (advisory)
|
|
194
|
+
runs-on: ubuntu-24.04
|
|
195
|
+
timeout-minutes: 45
|
|
196
|
+
continue-on-error: true
|
|
197
|
+
steps:
|
|
198
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
199
|
+
with:
|
|
200
|
+
persist-credentials: false
|
|
201
|
+
|
|
202
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
203
|
+
with:
|
|
204
|
+
python-version: "3.12"
|
|
205
|
+
cache: pip
|
|
206
|
+
cache-dependency-path: pyproject.toml
|
|
207
|
+
|
|
208
|
+
- name: Install SafeRun
|
|
209
|
+
run: python -m pip install -e '.[dev]'
|
|
210
|
+
|
|
211
|
+
- name: Print the runner's Docker and cgroup environment (diagnostic only)
|
|
212
|
+
# Read-only. Helps explain runner-specific differences, e.g. whether Docker reports OOMKilled for a killed container.
|
|
213
|
+
run: |
|
|
214
|
+
docker version
|
|
215
|
+
docker info
|
|
216
|
+
stat -fc %T /sys/fs/cgroup
|
|
217
|
+
uname -r
|
|
218
|
+
|
|
219
|
+
- name: Pull the pinned sandbox images and build the analyzer locally
|
|
220
|
+
# SafeRun pulls by pinned digest and verifies it; the analyzer is built from docker/analyzer (no signed release is used or needed).
|
|
221
|
+
run: saferun init --only python,node,analyzer --build-local
|
|
222
|
+
|
|
223
|
+
- name: Run the Docker-dependent tests
|
|
224
|
+
run: python -m pytest -q -ra -m docker
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Builds the SafeRun analyzer image, pushes it to ghcr.io and signs it with cosign (keyless, GitHub OIDC). Phase 5E.
|
|
2
|
+
#
|
|
3
|
+
# Triggered ONLY by pushing a tag `analyzer-v<number>[.<number>...]` whose commit is on main. The tag name is part of the signer
|
|
4
|
+
# identity that `saferun init` demands (saferun/sandbox/images.py: ANALYZER_SIGNER_IDENTITY_REGEXP), so a branch, a pull request or a
|
|
5
|
+
# renamed workflow produces a signature SafeRun refuses. The workflow prints the pushed manifest DIGEST: record it in
|
|
6
|
+
# ANALYZER_RELEASE (saferun/sandbox/images.py) in a reviewed commit; nothing updates the digest automatically.
|
|
7
|
+
#
|
|
8
|
+
# The image is built from the same recipe as docker/analyzer/build.sh (base image by digest, hash-locked Semgrep wheels,
|
|
9
|
+
# checksum-verified OSV-Scanner, rules checked against RULES_SHA256). tests/test_release_signature.py checks this file against images.py.
|
|
10
|
+
#
|
|
11
|
+
# Order, on purpose: build locally (NOT pushed) -> smoke-test that exact image -> log in to ghcr.io -> push that same image -> sign its digest
|
|
12
|
+
# -> verify with SafeRun's identity expression -> check that a wrong identity is refused. Nothing is published unless every earlier step passed,
|
|
13
|
+
# and the registry login happens only after the tests. Signatures made by any other repository or workflow (including the repository this
|
|
14
|
+
# project was copied from) do not satisfy the identity expression and are never trusted.
|
|
15
|
+
name: release-analyzer
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
push:
|
|
19
|
+
tags: ["analyzer-v*"]
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: release-analyzer
|
|
26
|
+
cancel-in-progress: false
|
|
27
|
+
|
|
28
|
+
env:
|
|
29
|
+
IMAGE: ghcr.io/vinay-repalle/saferun-analyzer
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
release:
|
|
33
|
+
runs-on: ubuntu-24.04
|
|
34
|
+
timeout-minutes: 40
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
packages: write # push to ghcr.io
|
|
38
|
+
id-token: write # keyless cosign signing (GitHub OIDC token)
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
41
|
+
with:
|
|
42
|
+
fetch-depth: 0
|
|
43
|
+
persist-credentials: false
|
|
44
|
+
|
|
45
|
+
- name: Check the tag name and that its commit is on main
|
|
46
|
+
env:
|
|
47
|
+
TAG: ${{ github.ref_name }}
|
|
48
|
+
run: |
|
|
49
|
+
set -eu
|
|
50
|
+
printf '%s' "$TAG" | grep -Eq '^analyzer-v[0-9]+(\.[0-9]+)*$' || { echo "tag '$TAG' is not analyzer-v<number>[.<number>...]" >&2; exit 1; }
|
|
51
|
+
git fetch --no-tags origin main
|
|
52
|
+
git merge-base --is-ancestor "$GITHUB_SHA" origin/main || { echo "tag commit is not on main" >&2; exit 1; }
|
|
53
|
+
|
|
54
|
+
- name: Stage the build context (same files as docker/analyzer/build.sh)
|
|
55
|
+
id: ctx
|
|
56
|
+
run: |
|
|
57
|
+
set -eu
|
|
58
|
+
ctx="$RUNNER_TEMP/analyzer-ctx"
|
|
59
|
+
mkdir "$ctx"
|
|
60
|
+
cp docker/analyzer/Dockerfile docker/analyzer/fetch_verified.py docker/analyzer/semgrep-requirements.txt docker/analyzer/THIRD_PARTY_NOTICES.md LICENSE saferun/engine/rules/python.yml "$ctx/"
|
|
61
|
+
cp -r docker/analyzer/licenses "$ctx/licenses"
|
|
62
|
+
echo "path=$ctx" >> "$GITHUB_OUTPUT"
|
|
63
|
+
echo "rules_sha=$(sha256sum "$ctx/python.yml" | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
|
64
|
+
|
|
65
|
+
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
|
66
|
+
|
|
67
|
+
- name: Build the image locally (not pushed yet)
|
|
68
|
+
id: build
|
|
69
|
+
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
|
70
|
+
with:
|
|
71
|
+
context: ${{ steps.ctx.outputs.path }}
|
|
72
|
+
platforms: linux/amd64
|
|
73
|
+
load: true
|
|
74
|
+
push: false
|
|
75
|
+
provenance: false
|
|
76
|
+
sbom: false
|
|
77
|
+
build-args: RULES_SHA256=${{ steps.ctx.outputs.rules_sha }}
|
|
78
|
+
tags: ${{ env.IMAGE }}:${{ github.ref_name }}
|
|
79
|
+
labels: |
|
|
80
|
+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
|
81
|
+
org.opencontainers.image.revision=${{ github.sha }}
|
|
82
|
+
|
|
83
|
+
- name: Smoke-test the exact image that will be published
|
|
84
|
+
env:
|
|
85
|
+
TAG: ${{ github.ref_name }}
|
|
86
|
+
RULES_SHA: ${{ steps.ctx.outputs.rules_sha }}
|
|
87
|
+
run: |
|
|
88
|
+
set -eu
|
|
89
|
+
img="$IMAGE:$TAG"
|
|
90
|
+
pins=$(python3 -c 'import sys; sys.path.insert(0,"."); from saferun.sandbox import images as i; print(i.SEMGREP_VERSION, i.OSV_SCANNER_VERSION)')
|
|
91
|
+
semgrep_v=${pins%% *}
|
|
92
|
+
osv_v=${pins##* }
|
|
93
|
+
# Same restrictions SafeRun applies when it runs the analyzer: no network, read-only root, no capabilities, non-root.
|
|
94
|
+
run() { docker run --rm --network none --read-only --cap-drop ALL --security-opt no-new-privileges --user 65534:65534 \
|
|
95
|
+
--memory 1g --pids-limit 256 --tmpfs /tmp:rw,noexec,nosuid,size=64m -e HOME=/tmp "$@"; }
|
|
96
|
+
for pair in "saferun.analyzer=1" "saferun.semgrep.version=$semgrep_v" "saferun.osv-scanner.version=$osv_v" "saferun.rules.sha256=$RULES_SHA"; do
|
|
97
|
+
key=${pair%%=*}; want=${pair#*=}
|
|
98
|
+
got=$(docker image inspect --format "{{index .Config.Labels \"$key\"}}" "$img")
|
|
99
|
+
[ "$got" = "$want" ] || { echo "label $key is '$got', expected '$want'" >&2; exit 1; }
|
|
100
|
+
done
|
|
101
|
+
[ "$(docker image inspect --format '{{.Config.User}}' "$img")" = "65534:65534" ] || { echo "the image does not default to the unprivileged user" >&2; exit 1; }
|
|
102
|
+
[ "$(run "$img" semgrep --version)" = "$semgrep_v" ] || { echo "semgrep version mismatch" >&2; exit 1; }
|
|
103
|
+
run "$img" osv-scanner --version | grep -q "osv-scanner version: $osv_v" || { echo "osv-scanner version mismatch" >&2; exit 1; }
|
|
104
|
+
[ "$(run "$img" sha256sum /opt/saferun/rules/python.yml | cut -d' ' -f1)" = "$RULES_SHA" ] || { echo "baked rules differ from the repository rules" >&2; exit 1; }
|
|
105
|
+
# The third-party notices and license texts must be in the image.
|
|
106
|
+
for f in LICENSE THIRD_PARTY_NOTICES.md licenses/semgrep-LICENSE licenses/osv-scanner-v2.7.0-LICENSE licenses/osv-scanner-v2.7.0-go-modules.md licenses/osv-scanner-v2.7.0-go-module-licenses.txt; do
|
|
107
|
+
run "$img" test -s "/usr/share/doc/saferun-analyzer/$f" || { echo "missing in image: $f" >&2; exit 1; }
|
|
108
|
+
done
|
|
109
|
+
# The container must have no network.
|
|
110
|
+
if run "$img" python -c 'import socket; socket.create_connection(("1.1.1.1", 53), 3)' >/dev/null 2>&1; then
|
|
111
|
+
echo "the analyzer container could reach the network" >&2; exit 1
|
|
112
|
+
fi
|
|
113
|
+
# A real (tiny) scan with the baked rules must produce valid JSON that names the scanned file.
|
|
114
|
+
proj=$(mktemp -d); chmod 0755 "$proj"
|
|
115
|
+
printf 'import subprocess\nsubprocess.run("ls", shell=True)\n' > "$proj/sample.py"; chmod 0644 "$proj/sample.py"
|
|
116
|
+
run -v "$proj:/work:ro" "$img" semgrep scan --json --quiet --metrics=off --disable-version-check --no-git-ignore \
|
|
117
|
+
--config /opt/saferun/rules/python.yml /work > "$RUNNER_TEMP/semgrep.json"
|
|
118
|
+
python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); assert "/work/sample.py" in d["paths"]["scanned"], d["paths"]' "$RUNNER_TEMP/semgrep.json"
|
|
119
|
+
echo "smoke test passed for $img"
|
|
120
|
+
|
|
121
|
+
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
|
122
|
+
with:
|
|
123
|
+
registry: ghcr.io
|
|
124
|
+
username: ${{ github.actor }}
|
|
125
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
126
|
+
|
|
127
|
+
- name: Push the tested image and read back its digest
|
|
128
|
+
id: push
|
|
129
|
+
env:
|
|
130
|
+
TAG: ${{ github.ref_name }}
|
|
131
|
+
run: |
|
|
132
|
+
set -eu
|
|
133
|
+
docker push "$IMAGE:$TAG"
|
|
134
|
+
digest=$(docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$IMAGE:$TAG" | grep "^$IMAGE@" | head -n 1 | cut -d@ -f2)
|
|
135
|
+
printf '%s' "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo "unexpected digest '$digest'" >&2; exit 1; }
|
|
136
|
+
echo "digest=$digest" >> "$GITHUB_OUTPUT"
|
|
137
|
+
|
|
138
|
+
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
|
139
|
+
with:
|
|
140
|
+
cosign-release: v2.4.3
|
|
141
|
+
|
|
142
|
+
- name: Sign the pushed digest (keyless)
|
|
143
|
+
env:
|
|
144
|
+
DIGEST: ${{ steps.push.outputs.digest }}
|
|
145
|
+
run: |
|
|
146
|
+
set -eu
|
|
147
|
+
printf '%s' "$DIGEST" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo "unexpected digest '$DIGEST'" >&2; exit 1; }
|
|
148
|
+
cosign sign --yes "$IMAGE@$DIGEST"
|
|
149
|
+
|
|
150
|
+
- name: Verify with the exact identity expression SafeRun will use
|
|
151
|
+
env:
|
|
152
|
+
DIGEST: ${{ steps.push.outputs.digest }}
|
|
153
|
+
run: |
|
|
154
|
+
set -eu
|
|
155
|
+
regexp=$(python3 -c 'import re,sys; sys.path.insert(0,"."); from saferun.sandbox.images import ANALYZER_SIGNER_IDENTITY_REGEXP as r; print(r)')
|
|
156
|
+
cosign verify \
|
|
157
|
+
--certificate-identity-regexp "$regexp" \
|
|
158
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
159
|
+
"$IMAGE@$DIGEST" > /dev/null
|
|
160
|
+
|
|
161
|
+
- name: Check that a wrong signer identity is refused (fails closed)
|
|
162
|
+
env:
|
|
163
|
+
DIGEST: ${{ steps.push.outputs.digest }}
|
|
164
|
+
run: |
|
|
165
|
+
set -u
|
|
166
|
+
if cosign verify \
|
|
167
|
+
--certificate-identity-regexp '^https://github\.com/not-this-owner/not-this-repo/.*$' \
|
|
168
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
169
|
+
"$IMAGE@$DIGEST" > /dev/null 2>&1; then
|
|
170
|
+
echo "cosign accepted an identity it must refuse" >&2
|
|
171
|
+
exit 1
|
|
172
|
+
fi
|
|
173
|
+
echo "wrong identity refused, as required"
|
|
174
|
+
|
|
175
|
+
- name: Report an image that was pushed but not fully verified
|
|
176
|
+
if: ${{ always() && steps.push.outcome == 'success' && job.status != 'success' }}
|
|
177
|
+
env:
|
|
178
|
+
TAG: ${{ github.ref_name }}
|
|
179
|
+
run: |
|
|
180
|
+
{
|
|
181
|
+
echo "### WARNING: pushed but NOT signed and verified"
|
|
182
|
+
echo
|
|
183
|
+
echo "A step after the push failed. \`$IMAGE:$TAG\` exists in the registry without a verified signature."
|
|
184
|
+
echo "SafeRun refuses unsigned images. Do NOT record it in ANALYZER_RELEASE. Delete this package version in GitHub (Packages -> saferun-analyzer -> versions), fix the cause, and use a NEW tag."
|
|
185
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
186
|
+
|
|
187
|
+
- name: Print what to record in ANALYZER_RELEASE
|
|
188
|
+
env:
|
|
189
|
+
DIGEST: ${{ steps.push.outputs.digest }}
|
|
190
|
+
run: |
|
|
191
|
+
{
|
|
192
|
+
echo "### Analyzer image published and signed"
|
|
193
|
+
echo
|
|
194
|
+
echo "Record this in \`saferun/sandbox/images.py\` in a reviewed commit (rules must be unchanged since this tag):"
|
|
195
|
+
echo
|
|
196
|
+
echo '```python'
|
|
197
|
+
echo "ANALYZER_RELEASE = AnalyzerRelease(ANALYZER_REGISTRY_REPOSITORY, \"$DIGEST\", \"$(date -u +%F)\")"
|
|
198
|
+
echo '```'
|
|
199
|
+
echo
|
|
200
|
+
echo "Reference: \`$IMAGE@$DIGEST\` (tag \`${{ github.ref_name }}\`, commit \`${{ github.sha }}\`)"
|
|
201
|
+
echo "Check from any machine: \`saferun verify-images --signature\` (after recording it)."
|
|
202
|
+
echo
|
|
203
|
+
echo "Manual check without SafeRun (cosign 2.2 or newer):"
|
|
204
|
+
echo '```sh'
|
|
205
|
+
echo "cosign verify --certificate-identity-regexp '^https://github\\.com/${{ github.repository }}/\\.github/workflows/release-analyzer\\.yml@refs/tags/analyzer-v[0-9]+(\\.[0-9]+)*\$' --certificate-oidc-issuer https://token.actions.githubusercontent.com $IMAGE@$DIGEST"
|
|
206
|
+
echo '```'
|
|
207
|
+
echo "The package is private until you make it public once (docs/release-images.md, step 3)."
|
|
208
|
+
} >> "$GITHUB_STEP_SUMMARY"
|