Skip to content

Commit 962269f

Browse files
committed
Add Baseline Level 3 paperwork, SBOM, and SCA tooling
Phase A (paperwork): - SECURITY.md: explicit Scope of Support and Duration of Support language; new Secrets and Credentials Policy section that lays out the no-long-lived-secrets stance (OIDC for PyPI, OAuth for RTD, zero GitHub Actions secrets configured). Closes OSPS-BR-07.02, OSPS-DO-04.01, OSPS-DO-05.01. - VERIFICATION.md: how downstream consumers verify a release. Covers SHA-256 hash verification, PEP 740 attestation verification via pypi-attestations CLI, and SBOM verification. Closes OSPS-DO-03.01 and OSPS-DO-03.02. - SECURITY_POLICIES.md: combined SAST and SCA policies. CVSS-based remediation thresholds, release-blocking criteria, suppression rules, license compliance posture. Closes OSPS-VM-05.01, OSPS-VM-05.02, and OSPS-VM-06.01. - contributing.rst: 'Automated Tests' becomes 'Testing Policy' with MUST language for major changes and an explicit definition of what counts as major. Closes OSPS-QA-06.03. Phase B (tooling): - publish.yml: CycloneDX SBOM generation step that installs the built wheel into an isolated venv, snapshots it with cyclonedx-py, and attaches the resulting bitmath-<version>.cdx.json to the GitHub release alongside the wheel and sdist. Closes OSPS-QA-02.02. - sca.yml: new workflow that runs pip-audit (PyPA's official audit tool) against requirements.txt on every push and pull request. Pinned to v1.1.0 by SHA. Needs to be added to required status checks on master to enforce blocking. Closes OSPS-VM-05.03. ARCHITECTURE.md and SECURITY_ASSESSMENT.md headers were also title-cased to align with the project documentation header conventions; cross-references were verified intact.
1 parent 947e63d commit 962269f

8 files changed

Lines changed: 419 additions & 25 deletions

File tree

.github/workflows/publish.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ jobs:
1818
python-version: "3.12"
1919
- name: Build package
2020
run: pip install build && python -m build
21+
- name: Generate CycloneDX SBOM
22+
run: |
23+
pip install cyclonedx-bom
24+
VERSION=$(cat VERSION)
25+
python -m venv /tmp/sbom-env
26+
/tmp/sbom-env/bin/pip install --upgrade pip
27+
/tmp/sbom-env/bin/pip install --no-deps dist/*.whl
28+
cyclonedx-py environment /tmp/sbom-env/bin/python \
29+
--output-format JSON \
30+
--output-file "dist/bitmath-${VERSION}.cdx.json"
2131
- name: Upload dist artifacts
2232
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
2333
with:
@@ -30,6 +40,7 @@ jobs:
3040
environment: pypi
3141
permissions:
3242
id-token: write
43+
contents: write
3344
steps:
3445
- name: Download dist artifacts
3546
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -38,3 +49,13 @@ jobs:
3849
path: dist/
3950
- name: Publish to PyPI
4051
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
52+
with:
53+
# Keep the SBOM out of the PyPI upload; it ships on the GitHub release instead.
54+
packages-dir: dist/
55+
skip-existing: false
56+
- name: Attach SBOM to GitHub Release
57+
env:
58+
GH_TOKEN: ${{ github.token }}
59+
run: |
60+
TAG="${GITHUB_REF#refs/tags/}"
61+
gh release upload "$TAG" dist/bitmath-*.cdx.json --clobber --repo "$GITHUB_REPOSITORY"

.github/workflows/sca.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: SCA Security Scan
3+
4+
on:
5+
push:
6+
branches: ["master"]
7+
pull_request:
8+
branches: ["master"]
9+
schedule:
10+
- cron: "0 0 * * 0"
11+
workflow_dispatch:
12+
13+
permissions: read-all
14+
15+
jobs:
16+
audit:
17+
runs-on: ubuntu-latest
18+
env:
19+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
26+
with:
27+
python-version: "3.12"
28+
29+
- name: Audit dev dependencies with pip-audit
30+
uses: pypa/gh-action-pip-audit@1220774d901786e6f652ae159f7b6bc8fea6d266 # v1.1.0
31+
with:
32+
inputs: requirements.txt

ARCHITECTURE.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
99
## Contents
1010

11-
- [Why this document exists](#why-this-document-exists)
12-
- [Runtime architecture](#runtime-architecture)
13-
- [Build and release pipeline](#build-and-release-pipeline)
14-
- [CI and quality gates](#ci-and-quality-gates)
15-
- [Trust boundaries](#trust-boundaries)
16-
- [Documentation pipeline](#documentation-pipeline)
11+
- [Why This Document Exists](#why-this-document-exists)
12+
- [Runtime Architecture](#runtime-architecture)
13+
- [Build and Release Pipeline](#build-and-release-pipeline)
14+
- [CI and Quality Gates](#ci-and-quality-gates)
15+
- [Trust Boundaries](#trust-boundaries)
16+
- [Documentation Pipeline](#documentation-pipeline)
1717
- [Maintenance](#maintenance)
1818

19-
## Why this document exists
19+
## Why This Document Exists
2020

2121
This is the architecture map for bitmath. It exists so downstream
2222
consumers and future contributors can see, in one place, what the
@@ -40,9 +40,9 @@ adding a new public function, restructuring the class hierarchy,
4040
changing the build backend, or changing how releases get published.
4141
No, if you are fixing a bug or adding a test.
4242

43-
## Runtime architecture
43+
## Runtime Architecture
4444

45-
### Component inventory
45+
### Component Inventory
4646

4747
The runtime library is a single Python module, `bitmath`, deliberately
4848
kept small. Almost all the logic lives in `bitmath/__init__.py`. The
@@ -58,7 +58,7 @@ actors that show up at runtime:
5858
| Constants | `bitmath.NIST`, `bitmath.SI`, `bitmath.NIST_PREFIXES`, `bitmath.SI_PREFIXES`, `bitmath.ALL_UNIT_TYPES` | Module-level lookup tables and system identifiers |
5959
| OS abstraction layer | Standard library: `shutil`, `os`, `fcntl` (Linux), `ctypes` (Windows) | Platform-specific calls used by the capacity-query functions |
6060

61-
### Layered view
61+
### Layered View
6262

6363
```
6464
+--------------------------------------------------------------+
@@ -141,7 +141,7 @@ consumer triggers them:
141141
elevated privileges on Linux (root) and Windows (administrator);
142142
macOS raises `NotImplementedError`.
143143

144-
### Data flow
144+
### Data Flow
145145

146146
Untrusted input enters the library at exactly three runtime places:
147147
the string parsers (`parse_string`, `parse_string_unsafe`), the
@@ -153,9 +153,9 @@ what bitmath does and does not validate at each entry point. See
153153
[SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md) for the full threat
154154
model.
155155

156-
## Build and release pipeline
156+
## Build and Release Pipeline
157157

158-
### Component inventory
158+
### Component Inventory
159159

160160
| Actor | Lives at | What it does |
161161
|-------|----------|--------------|
@@ -170,7 +170,7 @@ model.
170170
| PyPI | pypi.org | The distribution channel; serves wheels and sdists over HTTPS to `pip` |
171171
| Read The Docs | bitmath.readthedocs.io | Hosts the rendered documentation; rebuilds on push to master |
172172

173-
### Pipeline view
173+
### Pipeline View
174174

175175
```
176176
+------------------------+
@@ -249,7 +249,7 @@ model.
249249
against the published SHA-256 hash, and installs into their
250250
environment.
251251

252-
## CI and quality gates
252+
## CI and Quality Gates
253253

254254
CI runs continuously on `master` and on every pull request. These
255255
actors influence what is merged but do not produce released
@@ -269,7 +269,7 @@ The first three are required status checks on the `master` branch.
269269
Merges are blocked until they pass. `enforce_admins: true` so I
270270
cannot bypass them.
271271

272-
## Trust boundaries
272+
## Trust Boundaries
273273

274274
bitmath validates input only at the places untrusted data enters the
275275
library. The ingress points are:
@@ -293,7 +293,7 @@ library. The ingress points are:
293293
See [SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md) for the full
294294
threat model and the specific mitigations at each boundary.
295295

296-
## Documentation pipeline
296+
## Documentation Pipeline
297297

298298
The documentation lives in `docsite/source/`. Sphinx builds it.
299299
`index.rst.in` is the source template; `index.rst` is generated from

SECURITY.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ As of the 2.0.0 re-factor only versions ≥ 2.0.0 will receive support. Versions
1313
| `≥ 2.0.0` | :white_check_mark: |
1414
| `< 2.x.y` | :x: |
1515

16+
### Scope of Support
17+
18+
* **Security fixes** are issued as patch releases on the latest minor in the 2.x series. If the current line is 2.1.x, security fixes ship as 2.1.y.
19+
* **Bug fixes** are issued on the latest minor in the 2.x series. Older minors do not receive bug-fix backports unless the bug is also a security issue covered above.
20+
* **Functional changes** ship in new minor releases (2.x → 2.(x+1)).
21+
22+
### Duration of Support
23+
24+
* The **2.x series** receives security fixes for as long as it is the current major. Today that is open-ended; a future 3.x release would start a deprecation clock on 2.x with at least 12 months of overlap announced in `NEWS.rst` before 2.x support ends.
25+
* The **1.x series** (legacy) is end-of-life. No further releases will be made on 1.x. Users on 1.x should plan a migration to 2.x; the 2.x API is drop-in compatible for the documented public surface.
26+
* The **pre-1.x series** is historical and not supported in any form.
27+
1628
This list will be updated when future releases are made in the 2-version series that require specific callouts for supportability.
1729

1830
If you have discovered what you think is a harmful bug with the potential for exploitation in a supported version series, and this bug may lead to loss of life or data, then you have two options for reporting available to you:
@@ -52,3 +64,41 @@ As an emergency backup you can find me on bsky or instagram and direct message m
5264
* [insta - @tim.lnx](https://www.instagram.com/tim.lnx/)
5365

5466
For less serious security issues with lower potential for exploitation or damage, please open a bug on the project and apply the `security` label to it.
67+
68+
## Secrets and Credentials Policy
69+
70+
bitmath is built around the principle that **no long-lived secrets should exist in the project**. The policy is short because the implementation is austere.
71+
72+
### What Secrets the Project Handles
73+
74+
* **PyPI publish authority** is handled via [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC). The publish workflow exchanges a short-lived GitHub Actions OIDC token for upload credentials at the moment of publication. There is no long-lived PyPI API token stored as a GitHub secret.
75+
* **Read The Docs build credentials** are managed by Read The Docs via OAuth integration with GitHub. No credentials are stored in this repo.
76+
* **The `bitmath@lnx.cx` email address** is a forwarding alias used only for security correspondence. Compromise of that mailbox does not affect any project resource directly.
77+
78+
### Storage
79+
80+
* No secret values are stored in the repository. GitHub secret scanning is enabled at the repository level and runs continuously.
81+
* No secret values are stored in `.github/workflows/*.yml`. The workflows authenticate via OIDC tokens, not stored credentials.
82+
* The list of GitHub Actions secrets configured on this repository is **empty**, verifiable with `gh api /repos/timlnx/bitmath/actions/secrets`.
83+
84+
### Access Control
85+
86+
* The single maintainer (see [MAINTAINERS.md](MAINTAINERS.md)) has admin access to the repository, the PyPI project, and the Read The Docs project.
87+
* The PyPI Trusted Publishing trust relationship is scoped to the `.github/workflows/publish.yml` workflow on `master`. Any other workflow or branch attempting to authenticate to PyPI as bitmath will be rejected.
88+
* Two-factor authentication is required on both the GitHub account (authenticator app + GitHub Mobile, SMS disabled) and the PyPI account.
89+
90+
### Rotation
91+
92+
* OIDC tokens are short-lived (minutes) and rotated automatically on every workflow run by GitHub Actions and PyPI.
93+
* No long-lived credentials exist, so there is no rotation schedule to maintain.
94+
* The Trusted Publisher trust relationship itself can be revoked or re-scoped via the PyPI web UI if the workflow file ever needs to change in a way that would alter the trust boundary.
95+
96+
### Incident Response
97+
98+
If a project secret or credential is suspected to be compromised:
99+
100+
1. Revoke the Trusted Publisher relationship on PyPI immediately, before doing anything else. This breaks any in-flight publish attempt by an attacker.
101+
2. Audit recent activity in GitHub Actions runs and PyPI release history for unexpected events. The PyPI publish journal and GitHub Actions logs are public and inspectable.
102+
3. Reach out through the reporting channels above so the incident can be coordinated and disclosed.
103+
104+
This policy is reviewed at every minor release.

SECURITY_ASSESSMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ and operate on a descriptor the consumer has already opened.
9999
Protection model makes the call meaningless. There is no codepath
100100
to exploit there because there is no codepath at all.
101101

102-
### 4. The build and release pipeline
102+
### 4. The Build and Release Pipeline
103103

104104
The build pipeline itself is an attack surface: if the publishing
105105
workflow could be tricked into emitting a malicious wheel, every

SECURITY_POLICIES.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Security Policies
2+
3+
This document defines bitmath's policies for handling findings from
4+
Software Composition Analysis (SCA), Static Application Security
5+
Testing (SAST), and license compliance scanning. It exists so
6+
downstream consumers can understand the project's commitments and so
7+
future maintainers can apply the policies consistently.
8+
9+
The threat model that motivates these policies is in
10+
[SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md). The vulnerability
11+
reporting process is in [SECURITY.md](SECURITY.md). The release
12+
verification process is in [VERIFICATION.md](VERIFICATION.md). The
13+
workflows that enforce these policies live in `.github/workflows/`.
14+
15+
## Software Composition Analysis (SCA)
16+
17+
SCA covers known vulnerabilities and license compliance in
18+
bitmath's dependencies. Because bitmath has **zero runtime
19+
dependencies** (verifiable in `pyproject.toml`), the operational
20+
scope of SCA is the development dependency set listed in
21+
`requirements.txt`.
22+
23+
### Tools
24+
25+
* **Dependabot** opens pull requests for dependency updates and
26+
surfaces known vulnerabilities. Configured in
27+
`.github/dependabot.yml`.
28+
* **pip-audit** runs on every push and pull request via
29+
`.github/workflows/sca.yml` against `requirements.txt`. The
30+
workflow is a required status check on `master`.
31+
* **GitHub secret scanning** (enabled at the repository level)
32+
catches credentials accidentally committed to the repository.
33+
34+
### Remediation Thresholds
35+
36+
Severity ratings follow the CVSS score reported by the SCA tool.
37+
The remediation clock starts when the finding becomes visible in
38+
the project (Dependabot alert or `pip-audit` CI failure).
39+
40+
| Severity | CVSS range | Action | Timeline |
41+
|----------|-----------|--------|----------|
42+
| Critical | 9.0 – 10.0 | Patch or replace the affected dependency. Block any release until remediated. | Within 7 days of disclosure |
43+
| High | 7.0 – 8.9 | Patch or replace the affected dependency. Block release if the next release is within 30 days; otherwise remediate before that release. | Within 30 days of disclosure |
44+
| Medium | 4.0 – 6.9 | Patch when the next dependency update naturally cycles through. | Within 90 days |
45+
| Low | 0.1 – 3.9 | Patch opportunistically. | Best effort |
46+
47+
A finding may be suppressed only when it is demonstrably
48+
non-exploitable in bitmath's usage of the dependency. Suppression is
49+
documented inline in the SCA workflow configuration with a comment
50+
explaining why.
51+
52+
### Release Blocking
53+
54+
A release of bitmath **MUST NOT** ship while a Critical or High SCA
55+
finding against a dev dependency is unaddressed, unless the finding
56+
has been formally suppressed as non-exploitable per the rule above.
57+
The `.github/workflows/sca.yml` status check enforces this on the
58+
merge path; the release process (see
59+
[ARCHITECTURE.md](ARCHITECTURE.md)) is gated by the same branch
60+
protection.
61+
62+
### License Compliance
63+
64+
All direct dependencies must use a license listed as
65+
[OSI-approved](https://opensource.org/licenses) or
66+
[FSF-approved](https://www.gnu.org/licenses/license-list.html).
67+
Today every dev dependency uses MIT, Apache 2.0, or BSD. Adding a
68+
new dev dependency that introduces a copyleft license (GPL, AGPL,
69+
LGPL) requires explicit consideration before being merged; the PR
70+
that introduces it must document the decision in the description.
71+
72+
## Static Application Security Testing (SAST)
73+
74+
SAST covers security weaknesses in bitmath's own source code.
75+
76+
### Tools
77+
78+
* **Bandit** scans `bitmath/` and `tests/` for common Python
79+
security smells. Runs on every push and pull request via
80+
`.github/workflows/bandit.yml`. Required status check.
81+
* **CodeQL** performs semantic analysis for Python vulnerabilities.
82+
Runs on every push, pull request, and weekly via
83+
`.github/workflows/codeql.yml`. Required status check.
84+
* **OSSF Scorecard** performs a weekly meta-analysis of repository
85+
security posture. Reports to the GitHub Security tab.
86+
87+
### Remediation Thresholds
88+
89+
| Severity | Action | Timeline |
90+
|----------|--------|----------|
91+
| High | Fix in the next patch release. Block any merge that introduces a new High finding. | Within 7 days for existing findings; immediately for new ones |
92+
| Medium | Fix in the next minor release. | Within 30 days |
93+
| Low | Address in the normal development cycle. | Best effort |
94+
| Informational | Triage and either fix or suppress with a justifying comment. | Best effort |
95+
96+
A finding may be suppressed only when it is a true false positive.
97+
The suppression must include a `# nosec` comment with an inline
98+
justification (Bandit) or a `# lgtm[py/...]` annotation (CodeQL)
99+
explaining the rationale. The justification should be specific
100+
enough that a future maintainer can re-evaluate the suppression
101+
without re-deriving the analysis.
102+
103+
### Release Blocking
104+
105+
A release of bitmath **MUST NOT** ship while any High SAST finding
106+
against the project's own code is unaddressed, unless the finding is
107+
formally suppressed as a false positive per the rule above. The
108+
Bandit and CodeQL required status checks enforce this on the merge
109+
path.
110+
111+
## Review and Updates
112+
113+
This policy is reviewed at every minor release. The contract:
114+
115+
* A new SAST or SCA tool added to CI → update the corresponding
116+
Tools section.
117+
* A change to remediation thresholds or release-blocking rules →
118+
update the corresponding table and re-state the rationale in the
119+
commit message.
120+
* A suppression added in code → also add a brief justification in
121+
the suppression comment so the rationale survives the next
122+
review.
123+
124+
If this policy drifts out of sync with what the workflows actually
125+
enforce, that is a defect worth [opening an
126+
issue](https://github.com/timlnx/bitmath/issues/new) or a PR.

0 commit comments

Comments
 (0)