From 8d16e05b8f36c0ed498458e34c5db4231dbd0eb3 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Sat, 23 May 2026 23:25:06 -0500 Subject: [PATCH 1/6] Add OpenSSF Best Practices badge --- README.rst | 3 +++ docsite/source/index.rst | 3 +++ docsite/source/index.rst.in | 3 +++ 3 files changed, 9 insertions(+) diff --git a/README.rst b/README.rst index eb514a1..fc85b1c 100644 --- a/README.rst +++ b/README.rst @@ -19,6 +19,9 @@ .. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath :alt: OSSF Scorecard +.. image:: https://www.bestpractices.dev/projects/12749/badge + :target: https://www.bestpractices.dev/en/projects/12749 + :alt: OpenSSF Best Practices .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues diff --git a/docsite/source/index.rst b/docsite/source/index.rst index d9364fd..ef70e99 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -19,6 +19,9 @@ .. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath :alt: OSSF Scorecard +.. image:: https://www.bestpractices.dev/projects/12749/badge + :target: https://www.bestpractices.dev/en/projects/12749 + :alt: OpenSSF Best Practices .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index 817ed6f..ef735e7 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -19,6 +19,9 @@ .. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath :alt: OSSF Scorecard +.. image:: https://www.bestpractices.dev/projects/12749/badge + :target: https://www.bestpractices.dev/en/projects/12749 + :alt: OpenSSF Best Practices .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues From 234b55be74613e30008c12f98ec26ed9d542a14e Mon Sep 17 00:00:00 2001 From: Tim Case Date: Sat, 23 May 2026 23:48:15 -0500 Subject: [PATCH 2/6] Add OpenSSF Baseline badge and group OSSF badges at top --- README.rst | 16 ++++++++++------ docsite/source/index.rst | 16 ++++++++++------ docsite/source/index.rst.in | 16 ++++++++++------ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index fc85b1c..769a2eb 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,13 @@ +.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge + :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath + :alt: OSSF Scorecard +.. image:: https://www.bestpractices.dev/projects/12749/badge + :target: https://www.bestpractices.dev/en/projects/12749 + :alt: OpenSSF Best Practices +.. image:: https://www.bestpractices.dev/projects/12749/baseline + :target: https://www.bestpractices.dev/projects/12749 + :alt: OpenSSF Baseline + .. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml .. image:: https://img.shields.io/pypi/v/bitmath.svg @@ -16,12 +26,6 @@ .. image:: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml :alt: Bandit Security Scan -.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge - :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath - :alt: OSSF Scorecard -.. image:: https://www.bestpractices.dev/projects/12749/badge - :target: https://www.bestpractices.dev/en/projects/12749 - :alt: OpenSSF Best Practices .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues diff --git a/docsite/source/index.rst b/docsite/source/index.rst index ef70e99..7033319 100644 --- a/docsite/source/index.rst +++ b/docsite/source/index.rst @@ -1,3 +1,13 @@ +.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge + :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath + :alt: OSSF Scorecard +.. image:: https://www.bestpractices.dev/projects/12749/badge + :target: https://www.bestpractices.dev/en/projects/12749 + :alt: OpenSSF Best Practices +.. image:: https://www.bestpractices.dev/projects/12749/baseline + :target: https://www.bestpractices.dev/projects/12749 + :alt: OpenSSF Baseline + .. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml .. image:: https://img.shields.io/pypi/v/bitmath.svg @@ -16,12 +26,6 @@ .. image:: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml :alt: Bandit Security Scan -.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge - :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath - :alt: OSSF Scorecard -.. image:: https://www.bestpractices.dev/projects/12749/badge - :target: https://www.bestpractices.dev/en/projects/12749 - :alt: OpenSSF Best Practices .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues diff --git a/docsite/source/index.rst.in b/docsite/source/index.rst.in index ef735e7..fdd1fe9 100644 --- a/docsite/source/index.rst.in +++ b/docsite/source/index.rst.in @@ -1,3 +1,13 @@ +.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge + :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath + :alt: OSSF Scorecard +.. image:: https://www.bestpractices.dev/projects/12749/badge + :target: https://www.bestpractices.dev/en/projects/12749 + :alt: OpenSSF Best Practices +.. image:: https://www.bestpractices.dev/projects/12749/baseline + :target: https://www.bestpractices.dev/projects/12749 + :alt: OpenSSF Baseline + .. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml .. image:: https://img.shields.io/pypi/v/bitmath.svg @@ -16,12 +26,6 @@ .. image:: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml :alt: Bandit Security Scan -.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge - :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath - :alt: OSSF Scorecard -.. image:: https://www.bestpractices.dev/projects/12749/badge - :target: https://www.bestpractices.dev/en/projects/12749 - :alt: OpenSSF Best Practices .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues From c36e48e0e138a31196842b2a9889081944dfec15 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Sun, 24 May 2026 00:09:27 -0500 Subject: [PATCH 3/6] Add MAINTAINERS, SECURITY_ASSESSMENT, and CVD response timeframes Closes OSPS Baseline Level 2 gaps: - OSPS-GV-01.01 / OSPS-GV-01.02: MAINTAINERS.md documents sole maintainer, sensitive resource access, responsibilities, and an open ask for a Debian/Ubuntu co-maintainer (issue #117). - OSPS-VM-01.01: SECURITY.md now states explicit response timeframes (72h acknowledgement, 7d initial triage, coordinated disclosure on patch readiness). - OSPS-SA-03.01: SECURITY_ASSESSMENT.md is a standing threat model covering the parser, filesystem helpers, query_device_capacity, and the build/release pipeline, plus the automated tooling stack (Bandit, CodeQL, OSSF Scorecard, Dependabot, secret scanning). --- MAINTAINERS.md | 94 ++++++++++++++++++++++++ SECURITY.md | 12 +++- SECURITY_ASSESSMENT.md | 158 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 MAINTAINERS.md create mode 100644 SECURITY_ASSESSMENT.md diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..f2bee40 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,94 @@ +# Maintainers + +bitmath is a one-person project, and has been since 2014. This file +exists so downstream consumers (and any future contributors) can see +exactly who has the keys to the kingdom and what those keys unlock. + +## Current Maintainer + +**Tim Case** (GitHub: [@timlnx](https://github.com/timlnx)) + +* Role: Project lead, sole maintainer +* Contact: `bitmath@lnx.cx` +* Bluesky: [@lnx.cx](https://bsky.app/profile/lnx.cx) +* Homepage: [Technitribe](https://blog.lnx.cx) +* Formerly: Tim Bielawa (`tbielawa`) + +## Access to Sensitive Resources + +I have admin access to the following resources. Nobody else does. + +* The [timlnx/bitmath](https://github.com/timlnx/bitmath) GitHub + repository (admin role, including the ability to change branch + protection rules, manage secrets, and modify security settings). +* The `bitmath` project on [PyPI](https://pypi.org/project/bitmath/). + Publishing happens through PyPI Trusted Publishing (OIDC), so there + is no long-lived API token to leak. The trust relationship is + configured in PyPI's web UI and is scoped to the + `.github/workflows/publish.yml` workflow on the `master` branch. + PyPI account has 2fa enabled. +* The [bitmath on Read The Docs](https://bitmath.readthedocs.io/) + documentation project. +* The `bitmath@lnx.cx` email address used for private vulnerability + reports (see [SECURITY.md](SECURITY.md)). + +If you ever see a release on PyPI, a tag pushed to master, or a +security advisory published that did not originate from me, treat it +as a compromise and reach out immediately through any of the contact +channels in [SECURITY.md](SECURITY.md). + +## Responsibilities + +As the sole maintainer I am responsible for: + +* Reviewing and merging pull requests. +* Cutting releases (version bumps, `NEWS.rst` entries, tags, PyPI + publication via the OIDC workflow). +* Triaging security reports per the timelines in + [SECURITY.md](SECURITY.md). +* Enforcing the [Code of Conduct](CODE_OF_CONDUCT.md). Note that I am + both the sole enforcer and the sole maintainer, which means if a + Code of Conduct concern involves me directly, the project itself + cannot offer you a neutral venue. Your remedies in that case are + the usual ones available against any open source maintainer: public + comment on the issue tracker, a fork, or moving to a different + library. I would rather be honest about that limitation than + pretend the project has a governance structure it does not have. +* Keeping the dependency surface honest. bitmath has zero runtime + dependencies on purpose; development dependencies are listed in + `requirements.txt`. + +## Decision Making + +Decisions are made by me, with input solicited from the issue and PR +discussions when the question is non-obvious or has visible downstream +impact. I try to write up the reasoning when something might surprise +people (see commit messages and `NEWS.rst` for the audit trail). + +## Becoming a Maintainer + +bitmath has not historically had additional maintainers, but the +project has been around for over a decade and I am not immortal. If +you are interested in helping carry this forward, open an issue or +reach out by email. Co-maintainership would start with regularly +landing reviewed contributions and graduate to write access after we +have built up some trust. + +### Open Maintainer Request: Debian / Ubuntu Packaging + +There is a standing ask tracked in [issue +#117](https://github.com/timlnx/bitmath/issues/117) ("Deb Maintainer +Wanted") and echoed in the [contributing +guide](https://bitmath.readthedocs.io/en/latest/contributing.html#supported-python-versions): +bitmath currently tracks the Python versions shipped in the current +and previous major RHEL releases via EPEL, because that is the +ecosystem I personally pay attention to. There is no official Debian +or Ubuntu release channel right now and I am looking for somebody to +take over `.deb` patching and the related distribution coverage. This +is a real way to get involved without having to take on the entire +project. Comment on #117, email me, or open a fresh issue if you want +to claim the role. + +## Inactive Maintainers + +None at present. diff --git a/SECURITY.md b/SECURITY.md index 48edfec..fe56738 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -34,7 +34,17 @@ You may also reach out to me at this email address: * `bitmath@lnx.cx` -Please include a tag in the subject indicating the sensitivity of the issue. I will respond and we will triage the issue, a disclosure statement will be made as soon as we understand the potential impact and have prepared a mitigation. +Please include a tag in the subject indicating the sensitivity of the issue. + +## Response Timeframes + +I treat security reports as the top of my queue. The commitments I make to anybody who files one: + +* **Acknowledgement of receipt: within 72 hours.** This is just a "got it, working on it" reply so you know the report did not get lost. If you do not hear back in three days, the email may not have made it; try one of the backup channels below. +* **Initial triage assessment: within 7 days.** This is where I tell you whether the report is reproducible, what the rough severity looks like, and what the disclosure timeline will be. +* **Coordinated disclosure: as soon as we have a fix prepared and an impact statement we both agree on.** I aim to publish a [GitHub Security Advisory](https://github.com/timlnx/bitmath/security/advisories) and a `NEWS.rst` entry alongside the patch release that contains the fix. If the issue is severe enough that disclosure has to happen before a fix is ready, we coordinate the timing together. + +These are commitments, not guarantees. bitmath is maintained by one person on the side. If I am on a plane or in a hospital the clock will keep ticking and I will catch up when I land. The backup channels below exist precisely for "I tried email and got nothing back" situations. As an emergency backup you can find me on bsky or instagram and direct message me there: diff --git a/SECURITY_ASSESSMENT.md b/SECURITY_ASSESSMENT.md new file mode 100644 index 0000000..1997a68 --- /dev/null +++ b/SECURITY_ASSESSMENT.md @@ -0,0 +1,158 @@ +# Security Assessment + +This document is bitmath's standing security assessment. The goal here +is not to claim the library is bulletproof. It is to lay out, in plain +language, what the realistic threats against bitmath actually look +like, what is already in place to address them, and where the soft +spots are. Downstream consumers and packagers should be able to read +this and form their own opinion without having to reverse-engineer the +project. + +I revisit this document at every minor release and whenever a new +feature introduces a new attack surface. + +## What bitmath Is (and What It Isn't) + +bitmath is a pure-Python library for representing and converting file +sizes between SI (decimal) and NIST (binary) unit systems. It does +arithmetic, comparisons, parsing, and formatting on size values. + +The most useful framing for the threat model is what bitmath +deliberately **does not** do: + +* It does **not** open network sockets, make HTTP requests, or talk to + any external service. +* It does **not** call `eval`, `exec`, or `compile` on user input. +* It does **not** shell out to subprocesses by default. The + `query_capacity` and `query_device_capacity` functions call into + platform APIs (`shutil.disk_usage`, `fcntl.ioctl`, `ctypes` against + Win32) but not via the shell. +* It has **zero runtime dependencies**. The only code that runs in a + bitmath consumer's process is bitmath itself, the Python standard + library, and the consumer's own application code. +* It does **not** write to disk, modify the filesystem, or maintain + any persistent state. + +This drastically narrows the realistic threat surface compared to most +Python packages on PyPI. + +## Attack Surfaces + +These are the places where bitmath consumes input that might originate +from a hostile source, ranked roughly by how worried I am about them. + +### 1. `parse_string()` and `parse_string_unsafe()` + +These take a string and return a bitmath object. If a consumer hands +bitmath an attacker-controlled string (web form input, log line, +config file from an untrusted source), the parser is in the hot path. + +**What we do about it:** + +* The parser is a regex-driven state machine. It does not call `eval` + on any portion of the input. +* Unknown unit suffixes raise `ValueError`. Malformed numeric portions + raise `ValueError`. The function will not silently coerce or accept + nonsense. +* The hypothesis-based fuzzing tests added in 2026 (`tests/test_hypothesis.py`) + exercise the parser with thousands of generated inputs per release + cycle to catch crash-on-input regressions. +* The known parser quirk to be aware of: scientific-notation numbers + do not round-trip cleanly through `parse_string` in all cases. This + is documented and tracked, and does not constitute a security + vulnerability (it is a precision issue, not a code-execution + surface). + +### 2. `getsize()`, `listdir()`, `query_capacity()` + +These take a filesystem path and call into the operating system. If a +consumer hands bitmath an attacker-controlled path, the standard +filesystem-API risks apply ([TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use), +symlink races, path traversal in the *consumer's* code that +constructed the path). + +**What we do about it:** + +* bitmath does no path sanitization of its own. It is the consumer's + responsibility to validate paths before passing them in. This is + documented behavior, not negligence; sanitization rules depend on + the consumer's threat model in ways the library cannot know. +* `listdir()` was deprecated in 2.1.0 because the API is awkward + enough that misuse was likely. Consumers should reach for + `os.walk()` plus `bitmath.getsize()` directly. + +### 3. `query_device_capacity()` + +This issues `fcntl.ioctl` calls on Linux and Win32 `DeviceIoControl` +calls on Windows against a file descriptor for a block device. Both +require elevated privileges (root on Linux, administrator on Windows) +and operate on a descriptor the consumer has already opened. + +**What we do about it:** + +* The Linux path uses fixed `ioctl` constants from the kernel headers + and writes only into bytes buffers it allocated itself. The Windows + path uses fixed control codes from the Win32 API. +* There is no string formatting into the ioctl argument and no + untrusted-input pathway into the kernel call. +* macOS raises `NotImplementedError` because the System Integrity + Protection model makes the call meaningless. There is no codepath + to exploit there because there is no codepath at all. + +### 4. The build and release pipeline + +The build pipeline itself is an attack surface: if the publishing +workflow could be tricked into emitting a malicious wheel, every +downstream pip install would be compromised. + +**What we do about it:** + +* Publishing uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC). + No long-lived PyPI token exists to be stolen. +* The publish workflow (`.github/workflows/publish.yml`) only fires on + `release: published` events. It cannot be triggered by a pull + request or a direct push. +* All GitHub Actions are SHA-pinned, not version-pinned. Action + upgrades go through Dependabot PRs that I review individually. +* Branch protection on `master` requires all CI checks to pass, with + `enforce_admins: true`. I cannot push a malicious commit directly + even if my account were compromised, as long as the branch + protection itself is not also compromised. +* Two-factor authentication is required on my GitHub account + (authenticator app + GitHub Mobile, SMS disabled). + +## Automated Security Tooling + +Everything below runs on every push and pull request to `master`, plus +on a weekly schedule, and posts its findings to the GitHub Security +tab. + +| Tool | What it checks | Where it lives | +|------|---------------|----------------| +| [Bandit](https://bandit.readthedocs.io/) | Common Python security smells across `bitmath/` and `tests/` | `.github/workflows/bandit.yml` | +| [CodeQL](https://codeql.github.com/) | Deeper semantic analysis for Python vulnerabilities | `.github/workflows/codeql.yml` | +| [OSSF Scorecard](https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath) | Repository security posture (token permissions, pinning, signed releases, etc.) | `.github/workflows/scorecard.yml` | +| [Dependabot](https://docs.github.com/en/code-security/dependabot) | Vulnerability alerts and security update PRs for dependencies | `.github/dependabot.yml` | +| [GitHub Secret Scanning](https://docs.github.com/en/code-security/secret-scanning) | Prevents accidental commit of credentials | Repository setting (enabled) | + +Bandit currently reports zero findings against the codebase. CodeQL +currently reports zero findings against the codebase. OSSF Scorecard +currently rates the project at 7.8/10 and climbing as the OpenSSF +Best Practices badge work lands. + +## Known Vulnerabilities + +None at time of writing. If any are reported and confirmed, they will +be published as [GitHub Security Advisories](https://github.com/timlnx/bitmath/security/advisories) +with the CVE number when applicable, and called out in the relevant +`NEWS.rst` section for that release. + +## Reporting + +See [SECURITY.md](SECURITY.md) for the reporting process, response +timeframes, and contact channels. + +## Last Review + +This assessment was last reviewed against the bitmath 2.1.0 working +tree. From 8402ac672956ca1f7d58bf7e03cb5f09a8257d58 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Sun, 24 May 2026 00:42:03 -0500 Subject: [PATCH 4/6] Add ARCHITECTURE.md design documentation Closes the last OSPS Baseline Level 2 gap (OSPS-SA-01.01). The document covers two scopes: the runtime library (actors and actions inside the consumer's process) and the build and release pipeline (actors and actions that produce the released artifacts). CI gates, trust boundaries, and the documentation pipeline are summarized in their own sections with pointers to the source of truth for each. The contributing guide gets a new Project Architecture section that points contributors at ARCHITECTURE.md and states the update contract: any change to a public API, the build pipeline, or a trust boundary requires the corresponding architecture update in the same PR. --- ARCHITECTURE.md | 321 ++++++++++++++++++++++++++++++++ docsite/source/contributing.rst | 18 ++ 2 files changed, 339 insertions(+) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4b8df9f --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,321 @@ +# bitmath Architecture + +> This document exists to satisfy the OSPS Baseline Level 2 control +> [OSPS-SA-01.01](https://www.bestpractices.dev/en/projects/12749/baseline-2) +> (design documentation showing the actions and actors within the +> system), in addition to being useful for downstream consumers and +> contributors who want to understand how bitmath fits together. + +## Contents + +- [Why this document exists](#why-this-document-exists) +- [Runtime architecture](#runtime-architecture) +- [Build and release pipeline](#build-and-release-pipeline) +- [CI and quality gates](#ci-and-quality-gates) +- [Trust boundaries](#trust-boundaries) +- [Documentation pipeline](#documentation-pipeline) +- [Maintenance](#maintenance) + +## Why this document exists + +This is the architecture map for bitmath. It exists so downstream +consumers and future contributors can see, in one place, what the +moving parts are and how they fit together. It covers two things: the +runtime library (what runs in your process when you `import bitmath`), +and the build and release pipeline (how that code gets from this +repository onto your machine). + +It does not cover everything in the repository. Other concerns have +their own homes: + +* [MAINTAINERS.md](MAINTAINERS.md): who has access to what. +* [SECURITY.md](SECURITY.md): how to report a vulnerability. +* [SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md): the standing + threat model. +* [bitmath on Read The Docs](https://bitmath.readthedocs.io/): API + reference and tutorials. + +If you are wondering whether to update this file: yes, if you are +adding a new public function, restructuring the class hierarchy, +changing the build backend, or changing how releases get published. +No, if you are fixing a bug or adding a test. + +## Runtime architecture + +### Component inventory + +The runtime library is a single Python module, `bitmath`, deliberately +kept small. Almost all the logic lives in `bitmath/__init__.py`. The +actors that show up at runtime: + +| Actor | Lives at | What it does | +|-------|----------|--------------| +| User code | The consumer's process | Imports bitmath, calls module-level functions, instantiates unit classes, does arithmetic | +| Public module API | `bitmath.*` | Module-level entry points: `parse_string`, `parse_string_unsafe`, `best_prefix`, `getsize`, `listdir` (deprecated in 2.1.0), `query_capacity`, `query_device_capacity` | +| `Bitmath` base class | `bitmath.Bitmath` | The arithmetic, comparison, bitwise, and formatting engine. Every concrete unit class inherits from it | +| `Byte` family | `bitmath.Byte`, `KiB`, `MiB`, `GiB`, `TiB`, `PiB`, `EiB`, `kB`, `MB`, `GB`, `TB`, `PB`, `EB`, `ZB`, `YB` | Byte-based concrete unit classes (NIST binary and SI decimal) | +| `Bit` family | `bitmath.Bit`, `Kib`, `Mib`, `Gib`, `Tib`, `Pib`, `Eib`, `kb`, `Mb`, `Gb`, `Tb`, `Pb`, `Eb`, `Zb`, `Yb` | Bit-based concrete unit classes (NIST binary and SI decimal) | +| Constants | `bitmath.NIST`, `bitmath.SI`, `bitmath.NIST_PREFIXES`, `bitmath.SI_PREFIXES`, `bitmath.ALL_UNIT_TYPES` | Module-level lookup tables and system identifiers | +| OS abstraction layer | Standard library: `shutil`, `os`, `fcntl` (Linux), `ctypes` (Windows) | Platform-specific calls used by the capacity-query functions | + +### Layered view + +``` ++--------------------------------------------------------------+ +| User code | +| import bitmath; KiB(1024).MiB | ++--------------------------------------------------------------+ + | + v ++--------------------------------------------------------------+ +| Public module API (bitmath.*) | +| parse_string, best_prefix, getsize, query_capacity, etc. | ++--------------------------------------------------------------+ + | + v ++--------------------------------------------------------------+ +| Library core | +| Bitmath base class | +| Byte family (KiB, MiB, kB, MB, ...) | +| Bit family (Kib, Mib, kb, Mb, ...) | +| _norm_value: normalize every value to bits internally | ++--------------------------------------------------------------+ + | + v (only for getsize / listdir / + | query_capacity / + | query_device_capacity) ++--------------------------------------------------------------+ +| OS abstraction layer | +| shutil.disk_usage -- cross-platform capacity | +| fcntl.ioctl -- Linux block device capacity | +| ctypes / Win32 API -- Windows block device capacity | +| macOS: NotImplementedError (System Integrity Protection) | ++--------------------------------------------------------------+ +``` + +### Actions + +The things that happen at runtime, in roughly the order a typical +consumer triggers them: + +1. **Construction.** A user creates a bitmath instance (`KiB(1024)`, + `Byte(bits=8192)`, `parse_string("4.5 GiB")`). The constructor + passes the value through `_norm_value`, which converts everything + to an internal bits representation using the class-level + `_base_value` and `_unit_value` constants. After construction, the + instance carries one canonical value. + +2. **Conversion.** A user requests a different unit + (`KiB(1024).MiB`). Each unit class exposes properties that compute + their own value from the internal bits representation. No + conversion is cached; every property access does the math fresh. + +3. **Arithmetic.** Operators (`+`, `-`, `*`, `/`, `//`, `%`, + `divmod`) operate on the internal bits representation and return + a new instance. Mixed-unit arithmetic (`KiB(1) + MiB(1)`) + resolves through the bits representation, so the result is + unit-correct. + +4. **Comparison and bitwise.** Comparisons (`<`, `>`, `==`) and + bitwise operations (`&`, `|`, `^`, `~`, `<<`, `>>`) work on the + internal bits representation. + +5. **Formatting.** `str()`, `repr()`, and `format()` (including + f-string usage) produce human-readable output. The `best_prefix` + function picks the most readable unit for a given byte value. + +6. **Parsing.** `parse_string` and `parse_string_unsafe` accept a + string like `"4.5 GiB"` and return a bitmath instance. The strict + form raises on ambiguous unit; the unsafe form picks a default + system (SI by default) when the unit is ambiguous. + +7. **Filesystem inspection.** `getsize` accepts a path or + `pathlib.Path` and returns a bitmath instance for the file size. + `listdir` (deprecated in 2.1.0) walks a tree and yields sizes. + +8. **Capacity querying.** `query_capacity(path)` returns a + `Capacity(total, used, free)` named tuple of `Byte` instances. + This uses `shutil.disk_usage` and works on every supported + platform without elevated privileges. `query_device_capacity(fd)` + returns the raw physical block-device capacity and requires + elevated privileges on Linux (root) and Windows (administrator); + macOS raises `NotImplementedError`. + +### Data flow + +Untrusted input enters the library at exactly three runtime places: +the string parsers (`parse_string`, `parse_string_unsafe`), the +filesystem helpers (`getsize`, `listdir`), and the capacity functions +(`query_capacity`, `query_device_capacity`). For everything else the +input is a Python number or another bitmath instance, both of which +are inherently trusted. The trust boundaries section below covers +what bitmath does and does not validate at each entry point. See +[SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md) for the full threat +model. + +## Build and release pipeline + +### Component inventory + +| Actor | Lives at | What it does | +|-------|----------|--------------| +| Source repository | github.com/timlnx/bitmath | Single source of truth for all code, docs, and pipeline configuration | +| `VERSION` file | Repository root | Single source of the project version; read by Hatchling and the Makefile | +| `pyproject.toml` | Repository root | PEP 517/518/621 project metadata; declares Hatchling as the build backend | +| Hatchling | Build-time only | PyPA-blessed build backend; produces the wheel and sdist artifacts | +| `Makefile` | Repository root | Local automation; targets like `make ci`, `make build`, `make docs` | +| GitHub Actions runners | `.github/workflows/*.yml` | Hosted CI execution environment | +| `publish.yml` workflow | `.github/workflows/publish.yml` | Two-job workflow: build artifacts, then publish via OIDC | +| PyPI Trusted Publishing | PyPI's OIDC verifier | Validates the OIDC token from the `publish.yml` workflow and authorizes the upload | +| PyPI | pypi.org | The distribution channel; serves wheels and sdists over HTTPS to `pip` | +| Read The Docs | bitmath.readthedocs.io | Hosts the rendered documentation; rebuilds on push to master | + +### Pipeline view + +``` + +------------------------+ + | Developer | + | (see MAINTAINERS.md) | + +-----------+------------+ + | git push, git tag + v + +------------------------+ + | GitHub repository | + | timlnx/bitmath | + | branch protection on | + | master (17 required | + | checks, enforce | + | admins) | + +-----------+------------+ + | release: published + v + +------------------------+ + | publish.yml workflow | + | | + | Job 1: build | + | pip install build | + | python -m build | + | -> dist/ artifacts | + | | + | Job 2: publish | + | id-token: write | + | PyPI OIDC exchange | + +-----------+------------+ + | OIDC-authenticated upload + v + +------------------------+ + | PyPI | + | pypi.org/project | + | /bitmath/ | + | wheel + sdist | + | + PEP 740 | + | attestations | + +-----------+------------+ + | pip install bitmath + v + +------------------------+ + | Consumer's process | + +------------------------+ +``` + +### Actions + +1. **Version bump.** I edit `VERSION` to the new SemVer string. + `pyproject.toml` reads this dynamically via `[tool.hatch.version]`; + the Makefile reads it for docs and man pages. There is no other + place to update. + +2. **Changelog.** I add the section for the new version to + `NEWS.rst`. + +3. **Tag and push.** A signed tag is pushed to GitHub. + +4. **Create a GitHub Release.** Releasing from the tag fires the + `release: published` event. The `publish.yml` workflow has no + other trigger; it cannot fire on pull requests or direct pushes. + +5. **Build job.** Runs on a clean ubuntu-latest runner with Python + 3.12. Installs `build`, runs `python -m build`, uploads the + `dist/` directory as a workflow artifact. + +6. **Publish job.** Depends on the build job. Downloads the artifact. + Requests an OIDC token (`id-token: write`) from GitHub Actions and + exchanges it with PyPI for short-lived upload credentials via + Trusted Publishing. Uploads the wheel and sdist. No long-lived + PyPI API token exists anywhere in the project. + +7. **Consumer install.** A consumer runs `pip install bitmath`. + `pip` fetches the wheel over HTTPS from PyPI, verifies the package + against the published SHA-256 hash, and installs into their + environment. + +## CI and quality gates + +CI runs continuously on `master` and on every pull request. These +actors influence what is merged but do not produce released +artifacts. They are summarized here for completeness; the source of +truth for each is the corresponding workflow file in +`.github/workflows/`. + +| Actor | Workflow | What it gates | +|-------|----------|---------------| +| pytest matrix | `python.yml` | Full test suite across Python 3.9–3.13 on macOS, Ubuntu, Windows | +| Bandit | `bandit.yml` | Python security smell analysis across `bitmath/` and `tests/` | +| CodeQL | `codeql.yml` | Semantic analysis for Python vulnerabilities | +| OSSF Scorecard | `scorecard.yml` | Weekly repository security posture report | +| Dependabot | `.github/dependabot.yml` | Automated dependency update pull requests | + +The first three are required status checks on the `master` branch. +Merges are blocked until they pass. `enforce_admins: true` so I +cannot bypass them. + +## Trust boundaries + +bitmath validates input only at the places untrusted data enters the +library. The ingress points are: + +* **String parsing** (`parse_string`, `parse_string_unsafe`). + Regex-driven, no `eval`, raises `ValueError` on anything + malformed. Hypothesis-based fuzzing tests exercise this path. +* **Filesystem paths** (`getsize`, `listdir`). bitmath does no path + sanitization. The consumer is responsible for validating paths + before passing them in. This is documented behavior; sanitization + rules depend on the consumer's threat model in ways the library + cannot anticipate. +* **File descriptors** (`query_device_capacity`). Operates only on + descriptors the consumer already opened. The ioctl arguments are + fixed constants; no untrusted input flows into the kernel call. +* **The build pipeline.** `publish.yml` cannot be triggered by a + pull request, only by a release event. Untrusted contributor code + never touches PyPI credentials. Branch protection prevents direct + pushes to master from bypassing CI. + +See [SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md) for the full +threat model and the specific mitigations at each boundary. + +## Documentation pipeline + +The documentation lives in `docsite/source/`. Sphinx builds it. +`index.rst.in` is the source template; `index.rst` is generated from +`index.rst.in` plus `README.rst` by `make docs`. Editing `index.rst` +directly is wrong because the next `make docs` will overwrite it. + +Read The Docs runs the same build automatically on push to master +and publishes the result at https://bitmath.readthedocs.io/. + +## Maintenance + +This document is reviewed at every minor release. The contract: + +* New public API → update the Runtime architecture component + inventory and the actions list. +* New CI workflow or build pipeline change → update Build and + release pipeline and/or CI and quality gates. +* New trust boundary → update Trust boundaries here and the threat + model in [SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md). +* Build-backend or package-tooling change → update Build and release + pipeline. + +If this document drifts out of date, that is a defect worth +[opening an issue](https://github.com/timlnx/bitmath/issues/new) or +a PR. diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index f8aafb3..915a99e 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -34,6 +34,24 @@ provided template. * `View open issues `_ +.. _contributing_architecture: + +Project Architecture +******************** + +If you are about to make a non-trivial change, read the +`ARCHITECTURE.md +`_ +document in the repository root first. It is the map of how bitmath +is put together: the runtime library, the build and release +pipeline, the CI gates, and the trust boundaries. + +The contract is that ``ARCHITECTURE.md`` is reviewed at every minor +release and updated alongside any change to a public API, the build +pipeline, or a trust boundary. If you are touching one of those +things, your pull request should include the corresponding update. + + .. _contributing_code_style: Code Style and Formatting From 4d0f656f9b113e1a54de13c058bb954ce8f85431 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Sun, 24 May 2026 01:43:38 -0500 Subject: [PATCH 5/6] 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-.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. --- .github/workflows/publish.yml | 21 +++++ .github/workflows/sca.yml | 32 ++++++++ ARCHITECTURE.md | 34 ++++---- SECURITY.md | 50 ++++++++++++ SECURITY_ASSESSMENT.md | 2 +- SECURITY_POLICIES.md | 126 +++++++++++++++++++++++++++++ VERIFICATION.md | 137 ++++++++++++++++++++++++++++++++ docsite/source/contributing.rst | 42 ++++++++-- 8 files changed, 419 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/sca.yml create mode 100644 SECURITY_POLICIES.md create mode 100644 VERIFICATION.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1873c41..68815d3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,6 +18,16 @@ jobs: python-version: "3.12" - name: Build package run: pip install build && python -m build + - name: Generate CycloneDX SBOM + run: | + pip install cyclonedx-bom + VERSION=$(cat VERSION) + python -m venv /tmp/sbom-env + /tmp/sbom-env/bin/pip install --upgrade pip + /tmp/sbom-env/bin/pip install --no-deps dist/*.whl + cyclonedx-py environment /tmp/sbom-env/bin/python \ + --output-format JSON \ + --output-file "dist/bitmath-${VERSION}.cdx.json" - name: Upload dist artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -30,6 +40,7 @@ jobs: environment: pypi permissions: id-token: write + contents: write steps: - name: Download dist artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -38,3 +49,13 @@ jobs: path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + with: + # Keep the SBOM out of the PyPI upload; it ships on the GitHub release instead. + packages-dir: dist/ + skip-existing: false + - name: Attach SBOM to GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG="${GITHUB_REF#refs/tags/}" + gh release upload "$TAG" dist/bitmath-*.cdx.json --clobber --repo "$GITHUB_REPOSITORY" diff --git a/.github/workflows/sca.yml b/.github/workflows/sca.yml new file mode 100644 index 0000000..05d7f1e --- /dev/null +++ b/.github/workflows/sca.yml @@ -0,0 +1,32 @@ +--- +name: SCA Security Scan + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + schedule: + - cron: "0 0 * * 0" + workflow_dispatch: + +permissions: read-all + +jobs: + audit: + runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Audit dev dependencies with pip-audit + uses: pypa/gh-action-pip-audit@1220774d901786e6f652ae159f7b6bc8fea6d266 # v1.1.0 + with: + inputs: requirements.txt diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4b8df9f..2880b20 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -8,15 +8,15 @@ ## Contents -- [Why this document exists](#why-this-document-exists) -- [Runtime architecture](#runtime-architecture) -- [Build and release pipeline](#build-and-release-pipeline) -- [CI and quality gates](#ci-and-quality-gates) -- [Trust boundaries](#trust-boundaries) -- [Documentation pipeline](#documentation-pipeline) +- [Why This Document Exists](#why-this-document-exists) +- [Runtime Architecture](#runtime-architecture) +- [Build and Release Pipeline](#build-and-release-pipeline) +- [CI and Quality Gates](#ci-and-quality-gates) +- [Trust Boundaries](#trust-boundaries) +- [Documentation Pipeline](#documentation-pipeline) - [Maintenance](#maintenance) -## Why this document exists +## Why This Document Exists This is the architecture map for bitmath. It exists so downstream consumers and future contributors can see, in one place, what the @@ -40,9 +40,9 @@ adding a new public function, restructuring the class hierarchy, changing the build backend, or changing how releases get published. No, if you are fixing a bug or adding a test. -## Runtime architecture +## Runtime Architecture -### Component inventory +### Component Inventory The runtime library is a single Python module, `bitmath`, deliberately kept small. Almost all the logic lives in `bitmath/__init__.py`. The @@ -58,7 +58,7 @@ actors that show up at runtime: | Constants | `bitmath.NIST`, `bitmath.SI`, `bitmath.NIST_PREFIXES`, `bitmath.SI_PREFIXES`, `bitmath.ALL_UNIT_TYPES` | Module-level lookup tables and system identifiers | | OS abstraction layer | Standard library: `shutil`, `os`, `fcntl` (Linux), `ctypes` (Windows) | Platform-specific calls used by the capacity-query functions | -### Layered view +### Layered View ``` +--------------------------------------------------------------+ @@ -141,7 +141,7 @@ consumer triggers them: elevated privileges on Linux (root) and Windows (administrator); macOS raises `NotImplementedError`. -### Data flow +### Data Flow Untrusted input enters the library at exactly three runtime places: 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 [SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md) for the full threat model. -## Build and release pipeline +## Build and Release Pipeline -### Component inventory +### Component Inventory | Actor | Lives at | What it does | |-------|----------|--------------| @@ -170,7 +170,7 @@ model. | PyPI | pypi.org | The distribution channel; serves wheels and sdists over HTTPS to `pip` | | Read The Docs | bitmath.readthedocs.io | Hosts the rendered documentation; rebuilds on push to master | -### Pipeline view +### Pipeline View ``` +------------------------+ @@ -249,7 +249,7 @@ model. against the published SHA-256 hash, and installs into their environment. -## CI and quality gates +## CI and Quality Gates CI runs continuously on `master` and on every pull request. These 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. Merges are blocked until they pass. `enforce_admins: true` so I cannot bypass them. -## Trust boundaries +## Trust Boundaries bitmath validates input only at the places untrusted data enters the library. The ingress points are: @@ -293,7 +293,7 @@ library. The ingress points are: See [SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md) for the full threat model and the specific mitigations at each boundary. -## Documentation pipeline +## Documentation Pipeline The documentation lives in `docsite/source/`. Sphinx builds it. `index.rst.in` is the source template; `index.rst` is generated from diff --git a/SECURITY.md b/SECURITY.md index fe56738..3babaed 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,6 +13,18 @@ As of the 2.0.0 re-factor only versions ≥ 2.0.0 will receive support. Versions | `≥ 2.0.0` | :white_check_mark: | | `< 2.x.y` | :x: | +### Scope of Support + +* **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. +* **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. +* **Functional changes** ship in new minor releases (2.x → 2.(x+1)). + +### Duration of Support + +* 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. +* 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. +* The **pre-1.x series** is historical and not supported in any form. + This list will be updated when future releases are made in the 2-version series that require specific callouts for supportability. 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 * [insta - @tim.lnx](https://www.instagram.com/tim.lnx/) 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. + +## Secrets and Credentials Policy + +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. + +### What Secrets the Project Handles + +* **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. +* **Read The Docs build credentials** are managed by Read The Docs via OAuth integration with GitHub. No credentials are stored in this repo. +* **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. + +### Storage + +* No secret values are stored in the repository. GitHub secret scanning is enabled at the repository level and runs continuously. +* No secret values are stored in `.github/workflows/*.yml`. The workflows authenticate via OIDC tokens, not stored credentials. +* The list of GitHub Actions secrets configured on this repository is **empty**, verifiable with `gh api /repos/timlnx/bitmath/actions/secrets`. + +### Access Control + +* The single maintainer (see [MAINTAINERS.md](MAINTAINERS.md)) has admin access to the repository, the PyPI project, and the Read The Docs project. +* 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. +* Two-factor authentication is required on both the GitHub account (authenticator app + GitHub Mobile, SMS disabled) and the PyPI account. + +### Rotation + +* OIDC tokens are short-lived (minutes) and rotated automatically on every workflow run by GitHub Actions and PyPI. +* No long-lived credentials exist, so there is no rotation schedule to maintain. +* 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. + +### Incident Response + +If a project secret or credential is suspected to be compromised: + +1. Revoke the Trusted Publisher relationship on PyPI immediately, before doing anything else. This breaks any in-flight publish attempt by an attacker. +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. +3. Reach out through the reporting channels above so the incident can be coordinated and disclosed. + +This policy is reviewed at every minor release. diff --git a/SECURITY_ASSESSMENT.md b/SECURITY_ASSESSMENT.md index 1997a68..93c7bf2 100644 --- a/SECURITY_ASSESSMENT.md +++ b/SECURITY_ASSESSMENT.md @@ -99,7 +99,7 @@ and operate on a descriptor the consumer has already opened. Protection model makes the call meaningless. There is no codepath to exploit there because there is no codepath at all. -### 4. The build and release pipeline +### 4. The Build and Release Pipeline The build pipeline itself is an attack surface: if the publishing workflow could be tricked into emitting a malicious wheel, every diff --git a/SECURITY_POLICIES.md b/SECURITY_POLICIES.md new file mode 100644 index 0000000..a1da6d8 --- /dev/null +++ b/SECURITY_POLICIES.md @@ -0,0 +1,126 @@ +# Security Policies + +This document defines bitmath's policies for handling findings from +Software Composition Analysis (SCA), Static Application Security +Testing (SAST), and license compliance scanning. It exists so +downstream consumers can understand the project's commitments and so +future maintainers can apply the policies consistently. + +The threat model that motivates these policies is in +[SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md). The vulnerability +reporting process is in [SECURITY.md](SECURITY.md). The release +verification process is in [VERIFICATION.md](VERIFICATION.md). The +workflows that enforce these policies live in `.github/workflows/`. + +## Software Composition Analysis (SCA) + +SCA covers known vulnerabilities and license compliance in +bitmath's dependencies. Because bitmath has **zero runtime +dependencies** (verifiable in `pyproject.toml`), the operational +scope of SCA is the development dependency set listed in +`requirements.txt`. + +### Tools + +* **Dependabot** opens pull requests for dependency updates and + surfaces known vulnerabilities. Configured in + `.github/dependabot.yml`. +* **pip-audit** runs on every push and pull request via + `.github/workflows/sca.yml` against `requirements.txt`. The + workflow is a required status check on `master`. +* **GitHub secret scanning** (enabled at the repository level) + catches credentials accidentally committed to the repository. + +### Remediation Thresholds + +Severity ratings follow the CVSS score reported by the SCA tool. +The remediation clock starts when the finding becomes visible in +the project (Dependabot alert or `pip-audit` CI failure). + +| Severity | CVSS range | Action | Timeline | +|----------|-----------|--------|----------| +| Critical | 9.0 – 10.0 | Patch or replace the affected dependency. Block any release until remediated. | Within 7 days of disclosure | +| 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 | +| Medium | 4.0 – 6.9 | Patch when the next dependency update naturally cycles through. | Within 90 days | +| Low | 0.1 – 3.9 | Patch opportunistically. | Best effort | + +A finding may be suppressed only when it is demonstrably +non-exploitable in bitmath's usage of the dependency. Suppression is +documented inline in the SCA workflow configuration with a comment +explaining why. + +### Release Blocking + +A release of bitmath **MUST NOT** ship while a Critical or High SCA +finding against a dev dependency is unaddressed, unless the finding +has been formally suppressed as non-exploitable per the rule above. +The `.github/workflows/sca.yml` status check enforces this on the +merge path; the release process (see +[ARCHITECTURE.md](ARCHITECTURE.md)) is gated by the same branch +protection. + +### License Compliance + +All direct dependencies must use a license listed as +[OSI-approved](https://opensource.org/licenses) or +[FSF-approved](https://www.gnu.org/licenses/license-list.html). +Today every dev dependency uses MIT, Apache 2.0, or BSD. Adding a +new dev dependency that introduces a copyleft license (GPL, AGPL, +LGPL) requires explicit consideration before being merged; the PR +that introduces it must document the decision in the description. + +## Static Application Security Testing (SAST) + +SAST covers security weaknesses in bitmath's own source code. + +### Tools + +* **Bandit** scans `bitmath/` and `tests/` for common Python + security smells. Runs on every push and pull request via + `.github/workflows/bandit.yml`. Required status check. +* **CodeQL** performs semantic analysis for Python vulnerabilities. + Runs on every push, pull request, and weekly via + `.github/workflows/codeql.yml`. Required status check. +* **OSSF Scorecard** performs a weekly meta-analysis of repository + security posture. Reports to the GitHub Security tab. + +### Remediation Thresholds + +| Severity | Action | Timeline | +|----------|--------|----------| +| 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 | +| Medium | Fix in the next minor release. | Within 30 days | +| Low | Address in the normal development cycle. | Best effort | +| Informational | Triage and either fix or suppress with a justifying comment. | Best effort | + +A finding may be suppressed only when it is a true false positive. +The suppression must include a `# nosec` comment with an inline +justification (Bandit) or a `# lgtm[py/...]` annotation (CodeQL) +explaining the rationale. The justification should be specific +enough that a future maintainer can re-evaluate the suppression +without re-deriving the analysis. + +### Release Blocking + +A release of bitmath **MUST NOT** ship while any High SAST finding +against the project's own code is unaddressed, unless the finding is +formally suppressed as a false positive per the rule above. The +Bandit and CodeQL required status checks enforce this on the merge +path. + +## Review and Updates + +This policy is reviewed at every minor release. The contract: + +* A new SAST or SCA tool added to CI → update the corresponding + Tools section. +* A change to remediation thresholds or release-blocking rules → + update the corresponding table and re-state the rationale in the + commit message. +* A suppression added in code → also add a brief justification in + the suppression comment so the rationale survives the next + review. + +If this policy drifts out of sync with what the workflows actually +enforce, that is a defect worth [opening an +issue](https://github.com/timlnx/bitmath/issues/new) or a PR. diff --git a/VERIFICATION.md b/VERIFICATION.md new file mode 100644 index 0000000..efd98cb --- /dev/null +++ b/VERIFICATION.md @@ -0,0 +1,137 @@ +# Verifying a bitmath Release + +This document explains how to verify that a bitmath release was built +by this project and not tampered with in transit or at rest. It is +aimed at downstream consumers who need to demonstrate due diligence +(organizations subject to the EU Cyber Resilience Act, NIST SSDF, or +similar frameworks) and at anyone who just wants to understand what +guarantees the release process actually provides. + +The release pipeline that produces these artifacts is documented in +[ARCHITECTURE.md](ARCHITECTURE.md). The threat model that motivates +this verification stack is in +[SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md). + +## What You Can Verify + +For every bitmath release on PyPI, three independent properties are +verifiable without trusting the maintainer: + +1. **Integrity.** Each wheel and sdist has a published SHA-256 hash. + `pip` checks this automatically on install. The hash is also + visible on each file's page on PyPI. +2. **Authenticity.** Each release is built and published by the + `publish.yml` workflow in `timlnx/bitmath` on GitHub, using + GitHub Actions OIDC and PyPI Trusted Publishing. The proof of + that binding is a [PEP 740](https://peps.python.org/pep-0740/) + attestation attached to each release file. +3. **Provenance.** A CycloneDX Software Bill of Materials (SBOM) is + attached to each GitHub Release, describing the runtime + dependency closure of the wheel. bitmath has zero runtime + dependencies, so this SBOM is short by design. + +## Verifying Integrity + +`pip` verifies the SHA-256 hash on install. There is normally +nothing you have to do beyond `pip install bitmath`. If you want to +verify a downloaded artifact manually: + +```bash +# Get the expected hash from PyPI for, say, version 2.0.1 +curl -s https://pypi.org/pypi/bitmath/2.0.1/json \ + | jq -r '.urls[] | "\(.digests.sha256) \(.filename)"' + +# Compute the local hash and compare +shasum -a 256 bitmath-2.0.1-py3-none-any.whl +``` + +The hashes must match. If they do not, do not install the file. + +## Verifying Authenticity (PEP 740 Attestation) + +PyPI exposes attestations under each release file's API metadata. +The simplest way to verify them is with the +[pypi-attestations](https://pypi.org/project/pypi-attestations/) +CLI: + +```bash +pipx install pypi-attestations + +# Download the wheel you want to verify +pip download bitmath==2.0.1 --no-deps -d /tmp/verify-bitmath/ + +# Verify the attestation +pypi-attestations verify pypi \ + --repository timlnx/bitmath \ + /tmp/verify-bitmath/bitmath-2.0.1-py3-none-any.whl +``` + +The expected attestation identity is: + +* **Source repository**: `timlnx/bitmath` +* **Workflow path**: `.github/workflows/publish.yml` +* **Workflow ref**: `refs/tags/` (the git tag for the + release) + +If verification succeeds, the file was built by GitHub Actions on +`timlnx/bitmath` from a workflow file whose contents you can audit +in the repository at the corresponding tag. The whole signing path +uses [Sigstore](https://www.sigstore.dev/) under the hood. No +long-lived signing keys are involved. + +If verification fails, **do not install the file**. Treat it as a +potential supply chain compromise and reach out via the channels in +[SECURITY.md](SECURITY.md). + +## Verifying the SBOM + +A CycloneDX SBOM is attached to each GitHub Release as +`bitmath-.cdx.json`. Fetch it directly from the release +page: + +```bash +curl -L -o bitmath-2.0.1.cdx.json \ + https://github.com/timlnx/bitmath/releases/download/v2.0.1/bitmath-2.0.1.cdx.json +``` + +You can validate it with any CycloneDX-aware tool. For example, with +[cyclonedx-cli](https://github.com/CycloneDX/cyclonedx-cli): + +```bash +cyclonedx validate --input-file bitmath-2.0.1.cdx.json +``` + +For bitmath, the SBOM should show one primary component (bitmath +itself) plus the small set of components present in the install +environment. The runtime dependency list under bitmath should be +empty, because bitmath has no runtime dependencies by design. If the +SBOM shows additional runtime dependencies under bitmath itself, +that is a defect worth [opening an +issue](https://github.com/timlnx/bitmath/issues/new) about. + +## Why This Matters + +These verification steps let you confirm three things without +trusting me personally: + +* The bytes you have are the bytes PyPI published. (Hash + verification.) +* The bytes PyPI published came from this repository's release + workflow. (PEP 740 attestation verification.) +* The dependency surface is what the project claims it is. (SBOM + verification.) + +A compromise that bypasses all three would require simultaneously +breaking the GitHub Actions OIDC infrastructure, the Sigstore +signing chain, and PyPI's storage. That is the threat model the +verification stack is designed against. More plausible compromises +(a stolen API token, a phishing-compromised maintainer account, a +tampered wheel in transit, a malicious dependency injection) are +caught by one or more of these checks. + +## Last Review + +This document is reviewed at every minor release alongside +[SECURITY_ASSESSMENT.md](SECURITY_ASSESSMENT.md), +[ARCHITECTURE.md](ARCHITECTURE.md), and +[SECURITY_POLICIES.md](SECURITY_POLICIES.md). diff --git a/docsite/source/contributing.rst b/docsite/source/contributing.rst index 915a99e..defa480 100644 --- a/docsite/source/contributing.rst +++ b/docsite/source/contributing.rst @@ -134,16 +134,44 @@ If anybody wants to take over Debian/Ubuntu patching, we can add notes for which distributions are covered. -.. _contributing_automated_tests: +.. _contributing_testing_policy: -Automated Tests -*************** +Testing Policy +************** + +The bitmath testing policy is short: **every major change to the +library MUST be accompanied by tests in the same pull request.** + +A "major change" for the purposes of this policy means any of: + +* a new public function, method, class, or property +* a change to the behavior of an existing public API +* a bug fix whose root cause is reproducible by a test (which is + almost all of them) +* a change to the internal arithmetic, parsing, or normalization + logic + +A "non-major change" that does not require new tests: + +* documentation changes +* comment-only changes +* CI workflow tweaks that do not affect the build matrix +* changes to packaging metadata that do not change runtime behavior + +All bitmath code includes unit tests to verify expected +functionality. New tests should: -Write `unittests `_ -for any new functionality if you are up to the task. It is not a hard -requirement, but it greatly helps. +* live in ``tests/`` as a ``test_*.py`` file +* have a unique test case name across the whole suite + (``tests/test_unique_testcase_names.sh`` enforces this) +* be written using `unittest + `_ (the existing + style) unless there is a specific reason to reach for + ``pytest``-native features -All bitmath code includes unit tests to verify expected functionality. +If you are unsure how to write a test for a particular change, open +the pull request anyway and ask in the description. I will help you +scope an appropriate test. .. _contributing_components: From 368ad8ee842545a5e85dbec6bc9ec16081d77e92 Mon Sep 17 00:00:00 2001 From: Tim Case Date: Sun, 24 May 2026 01:51:01 -0500 Subject: [PATCH 6/6] Document Packit RPM distribution pipeline in ARCHITECTURE.md The release pipeline diagram only covered the PyPI flow, but the same release event also fires Packit to build and ship Fedora and EPEL RPMs in parallel. Add the missing components (.packit.yaml, python-bitmath.spec, Packit, Copr, dist-git, Koji, Bodhi) to the component inventory and a second diagram showing the RPM distribution path alongside the existing PyPI path. Notes the known-stale target matrix (fedora-rawhide, fedora-42, epel-9, epel-10) so the document does not pretend to be current on that front. --- ARCHITECTURE.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2880b20..a93a1db 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -169,6 +169,13 @@ model. | PyPI Trusted Publishing | PyPI's OIDC verifier | Validates the OIDC token from the `publish.yml` workflow and authorizes the upload | | PyPI | pypi.org | The distribution channel; serves wheels and sdists over HTTPS to `pip` | | Read The Docs | bitmath.readthedocs.io | Hosts the rendered documentation; rebuilds on push to master | +| `.packit.yaml` | Repository root | Packit configuration; declares which release events trigger Copr scratch builds, dist-git PRs, Koji builds, and Bodhi updates | +| `python-bitmath.spec` | Repository root | RPM packaging spec used by Packit to build Fedora and EPEL RPMs | +| Packit | packit.dev | Hosted automation that connects upstream releases to Fedora dist-git, Copr, Koji, and Bodhi based on `.packit.yaml` | +| Copr | copr.fedorainfracloud.org/coprs/tbielawa/bitmath-ci | Fedora's build-on-demand farm; used for PR scratch builds and release-candidate builds | +| Fedora dist-git | src.fedoraproject.org/rpms/python-bitmath | Where the official RPM source lives for each Fedora/EPEL branch | +| Koji | koji.fedoraproject.org | Fedora's official build system; produces the RPMs that ship to users | +| Bodhi | bodhi.fedoraproject.org | Fedora's update system; pushes Koji-built RPMs to stable for f42, epel9, and epel10 | ### Pipeline View @@ -218,6 +225,80 @@ model. +------------------------+ ``` +### RPM Distribution Path (Fedora and EPEL via Packit) + +The same `release: published` event that triggers `publish.yml` also +fires the Packit pipeline. The two paths run in parallel and are +independent of each other; PyPI consumers get the wheel, Fedora and +EPEL consumers get an RPM. Production releases (clean `vX.Y.Z` tags) +flow through `propose_downstream` to dist-git, Koji, and Bodhi. +Release-candidate tags (`vX.Y.Z-rcN`) take a shorter path that only +produces Copr builds for testing. + +``` + +------------------------+ + | release: published | + | (same event that fires | + | the publish.yml flow | + | above) | + +-----------+------------+ + | reads .packit.yaml + v + python-bitmath.spec + +------------------------+ + | Packit | + | | + | clean vX.Y.Z tag: | + | propose_downstream | + | opens dist-git PRs | + | for rawhide, f42, | + | epel9, epel10 | + | | + | vX.Y.Z-rcN tag: | + | copr_build only | + +-----------+------------+ + | dist-git PR merged + v + +------------------------+ + | Fedora dist-git | + | src.fedoraproject | + | .org/rpms/ | + | python-bitmath | + +-----------+------------+ + | commit trigger + v + +------------------------+ + | Koji | + | builds RPMs from | + | each dist-git branch | + +-----------+------------+ + | tagged build + v + +------------------------+ + | Bodhi | + | update_type: | + | enhancement; | + | stable push for | + | f42, epel9, epel10 | + | (rawhide skipped) | + +-----------+------------+ + | dnf install python-bitmath + v + +------------------------+ + | Fedora / EPEL consumer | + +------------------------+ +``` + +The Copr scratch-build job that fires on every pull request is not +shown above; it is a CI-style validation, not part of the release +pipeline. It uses the same `.packit.yaml` and spec file to confirm +the RPM still builds across the targeted Fedora and EPEL branches +before a change merges. + +**Known limitation**: the target matrix in `.packit.yaml` today is +`fedora-rawhide`, `fedora-42`, `epel-9`, and `epel-10`. As new +Fedora and EPEL releases land, that list needs to be refreshed. That +work is tracked outside this document. + ### Actions 1. **Version bump.** I edit `VERSION` to the new SemVer string.