pqcprobe 0.1.0__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.
- pqcprobe-0.1.0/.gitignore +24 -0
- pqcprobe-0.1.0/LICENSE +21 -0
- pqcprobe-0.1.0/PKG-INFO +127 -0
- pqcprobe-0.1.0/README.md +91 -0
- pqcprobe-0.1.0/docs/DECISIONS.md +222 -0
- pqcprobe-0.1.0/pqcprobe.py +987 -0
- pqcprobe-0.1.0/pyproject.toml +92 -0
- pqcprobe-0.1.0/requirements.txt +156 -0
- pqcprobe-0.1.0/tests/__init__.py +2 -0
- pqcprobe-0.1.0/tests/test_pqcprobe_basic.py +135 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Virtual environments
|
|
2
|
+
.venv/
|
|
3
|
+
venv/
|
|
4
|
+
env/
|
|
5
|
+
|
|
6
|
+
# Python bytecode / caches
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
|
|
14
|
+
# Build artifacts
|
|
15
|
+
build/
|
|
16
|
+
dist/
|
|
17
|
+
*.egg
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
pqcprobe-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andre Van Klaveren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pqcprobe-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pqcprobe
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Probe a server's TLS configuration and post-quantum (ML-KEM) key-exchange readiness
|
|
5
|
+
Project-URL: Homepage, https://github.com/opratr/pqcprobe
|
|
6
|
+
Project-URL: Repository, https://github.com/opratr/pqcprobe
|
|
7
|
+
Project-URL: Issues, https://github.com/opratr/pqcprobe/issues
|
|
8
|
+
Author-email: Andre Van Klaveren <andre@vanklaverens.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: audit,cryptography,harvest-now-decrypt-later,ml-kem,mlkem,post-quantum,pqc,security,ssl,tls
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Security :: Cryptography
|
|
24
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
25
|
+
Requires-Python: >=3.9.2
|
|
26
|
+
Requires-Dist: cryptography>=46.0.0
|
|
27
|
+
Requires-Dist: pyopenssl>=26.3.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: bandit; extra == 'dev'
|
|
30
|
+
Requires-Dist: build; extra == 'dev'
|
|
31
|
+
Requires-Dist: pip-audit; extra == 'dev'
|
|
32
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
34
|
+
Requires-Dist: twine; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# pqcprobe
|
|
38
|
+
|
|
39
|
+
[](https://github.com/opratr/pqcprobe/actions/workflows/python-tests.yml)
|
|
40
|
+
[](https://github.com/opratr/pqcprobe/actions/workflows/lint.yml)
|
|
41
|
+
[](https://github.com/opratr/pqcprobe/actions/workflows/codeql.yml)
|
|
42
|
+
[](https://www.python.org/downloads/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
A small command-line TLS probing utility that uses pyOpenSSL to inspect an HTTPS server's TLS configuration, with a focus on post-quantum readiness.
|
|
46
|
+
|
|
47
|
+
Features:
|
|
48
|
+
- Reports negotiated TLS version, cipher, ALPN and certificate summary
|
|
49
|
+
- Reports the negotiated key-exchange group and flags whether it is
|
|
50
|
+
post-quantum (e.g. `X25519MLKEM768`) or classical
|
|
51
|
+
- Assesses post-quantum posture: enumerates which key-exchange groups the
|
|
52
|
+
server accepts and flags "harvest-now, decrypt-later" (HNDL) risk when no
|
|
53
|
+
post-quantum key exchange is offered
|
|
54
|
+
- Verifies the certificate matches the requested hostname (SAN/CN, wildcard
|
|
55
|
+
and IP aware)
|
|
56
|
+
- Probes server support for TLS 1.3 and TLS 1.2
|
|
57
|
+
- Samples which TLS 1.2 ciphers the server accepts (and attempts TLS 1.3 ciphersuites where supported by OpenSSL)
|
|
58
|
+
- Can fetch raw PEM for the server certificate (--raw-cert)
|
|
59
|
+
- Concurrency option for probing (--concurrency)
|
|
60
|
+
- Human-friendly summary (--pretty) or JSON output (--json)
|
|
61
|
+
- Meaningful exit codes for scripting (see below)
|
|
62
|
+
|
|
63
|
+
Requirements:
|
|
64
|
+
- Python 3.9.2+
|
|
65
|
+
- pyOpenSSL, cryptography (installed automatically)
|
|
66
|
+
- OpenSSL 3.x recommended (3.5+ for post-quantum group support). On macOS the
|
|
67
|
+
system `openssl` is LibreSSL and cannot test the ML-KEM hybrid groups; install
|
|
68
|
+
OpenSSL 3.5+ (e.g. via Homebrew) for full functionality.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install pqcprobe
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This installs a `pqcprobe` command. To run from a source checkout instead:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python3 -m venv .venv
|
|
80
|
+
source .venv/bin/activate
|
|
81
|
+
python3 -m pip install -e .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pqcprobe https://example.com --pretty
|
|
88
|
+
pqcprobe example.com:443 --json
|
|
89
|
+
pqcprobe example.com:443 --raw-cert
|
|
90
|
+
|
|
91
|
+
# Post-quantum audit: fail (exit 3) if the server offers no PQC key exchange
|
|
92
|
+
pqcprobe https://example.com --fail-on-classical-only
|
|
93
|
+
# Skip group probing entirely (e.g. when the openssl CLI is unavailable)
|
|
94
|
+
pqcprobe https://example.com --no-groups
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
(From a source checkout without installing, use `python3 pqcprobe.py ...`.)
|
|
98
|
+
|
|
99
|
+
Post-quantum key-exchange probing:
|
|
100
|
+
- Enumerating group support forces individual groups via the native `openssl`
|
|
101
|
+
CLI, since pyOpenSSL does not expose a way to set the group list. OpenSSL 3.5+
|
|
102
|
+
is required for the ML-KEM hybrid groups (`X25519MLKEM768`, etc.). Groups the
|
|
103
|
+
local openssl doesn't recognize are reported as "not testable" rather than
|
|
104
|
+
"unsupported" (relevant on macOS, whose system openssl is LibreSSL).
|
|
105
|
+
- Reading the *negotiated* group uses pyOpenSSL's `Connection.get_group_name()`
|
|
106
|
+
and needs no external tools.
|
|
107
|
+
|
|
108
|
+
Exit codes:
|
|
109
|
+
- `0` success
|
|
110
|
+
- `1` handshake failed
|
|
111
|
+
- `2` certificate hostname mismatch (when verifying)
|
|
112
|
+
- `3` no post-quantum key exchange offered (only with `--fail-on-classical-only`)
|
|
113
|
+
|
|
114
|
+
Notes:
|
|
115
|
+
- Programmatic overriding of TLS 1.3 ciphersuites requires a recent OpenSSL + pyOpenSSL exposing `set_ciphersuites`.
|
|
116
|
+
- Cipher probing may produce handshake failures for many ciphers — the tool records successes and errors.
|
|
117
|
+
- Only use pqcprobe against systems you own or are authorized to test.
|
|
118
|
+
|
|
119
|
+
## Contributing
|
|
120
|
+
|
|
121
|
+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Security
|
|
122
|
+
issues should be reported privately per [SECURITY.md](SECURITY.md).
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
[MIT](LICENSE) © Andre Van Klaveren
|
|
127
|
+
|
pqcprobe-0.1.0/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# pqcprobe
|
|
2
|
+
|
|
3
|
+
[](https://github.com/opratr/pqcprobe/actions/workflows/python-tests.yml)
|
|
4
|
+
[](https://github.com/opratr/pqcprobe/actions/workflows/lint.yml)
|
|
5
|
+
[](https://github.com/opratr/pqcprobe/actions/workflows/codeql.yml)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
A small command-line TLS probing utility that uses pyOpenSSL to inspect an HTTPS server's TLS configuration, with a focus on post-quantum readiness.
|
|
10
|
+
|
|
11
|
+
Features:
|
|
12
|
+
- Reports negotiated TLS version, cipher, ALPN and certificate summary
|
|
13
|
+
- Reports the negotiated key-exchange group and flags whether it is
|
|
14
|
+
post-quantum (e.g. `X25519MLKEM768`) or classical
|
|
15
|
+
- Assesses post-quantum posture: enumerates which key-exchange groups the
|
|
16
|
+
server accepts and flags "harvest-now, decrypt-later" (HNDL) risk when no
|
|
17
|
+
post-quantum key exchange is offered
|
|
18
|
+
- Verifies the certificate matches the requested hostname (SAN/CN, wildcard
|
|
19
|
+
and IP aware)
|
|
20
|
+
- Probes server support for TLS 1.3 and TLS 1.2
|
|
21
|
+
- Samples which TLS 1.2 ciphers the server accepts (and attempts TLS 1.3 ciphersuites where supported by OpenSSL)
|
|
22
|
+
- Can fetch raw PEM for the server certificate (--raw-cert)
|
|
23
|
+
- Concurrency option for probing (--concurrency)
|
|
24
|
+
- Human-friendly summary (--pretty) or JSON output (--json)
|
|
25
|
+
- Meaningful exit codes for scripting (see below)
|
|
26
|
+
|
|
27
|
+
Requirements:
|
|
28
|
+
- Python 3.9.2+
|
|
29
|
+
- pyOpenSSL, cryptography (installed automatically)
|
|
30
|
+
- OpenSSL 3.x recommended (3.5+ for post-quantum group support). On macOS the
|
|
31
|
+
system `openssl` is LibreSSL and cannot test the ML-KEM hybrid groups; install
|
|
32
|
+
OpenSSL 3.5+ (e.g. via Homebrew) for full functionality.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install pqcprobe
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This installs a `pqcprobe` command. To run from a source checkout instead:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python3 -m venv .venv
|
|
44
|
+
source .venv/bin/activate
|
|
45
|
+
python3 -m pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pqcprobe https://example.com --pretty
|
|
52
|
+
pqcprobe example.com:443 --json
|
|
53
|
+
pqcprobe example.com:443 --raw-cert
|
|
54
|
+
|
|
55
|
+
# Post-quantum audit: fail (exit 3) if the server offers no PQC key exchange
|
|
56
|
+
pqcprobe https://example.com --fail-on-classical-only
|
|
57
|
+
# Skip group probing entirely (e.g. when the openssl CLI is unavailable)
|
|
58
|
+
pqcprobe https://example.com --no-groups
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
(From a source checkout without installing, use `python3 pqcprobe.py ...`.)
|
|
62
|
+
|
|
63
|
+
Post-quantum key-exchange probing:
|
|
64
|
+
- Enumerating group support forces individual groups via the native `openssl`
|
|
65
|
+
CLI, since pyOpenSSL does not expose a way to set the group list. OpenSSL 3.5+
|
|
66
|
+
is required for the ML-KEM hybrid groups (`X25519MLKEM768`, etc.). Groups the
|
|
67
|
+
local openssl doesn't recognize are reported as "not testable" rather than
|
|
68
|
+
"unsupported" (relevant on macOS, whose system openssl is LibreSSL).
|
|
69
|
+
- Reading the *negotiated* group uses pyOpenSSL's `Connection.get_group_name()`
|
|
70
|
+
and needs no external tools.
|
|
71
|
+
|
|
72
|
+
Exit codes:
|
|
73
|
+
- `0` success
|
|
74
|
+
- `1` handshake failed
|
|
75
|
+
- `2` certificate hostname mismatch (when verifying)
|
|
76
|
+
- `3` no post-quantum key exchange offered (only with `--fail-on-classical-only`)
|
|
77
|
+
|
|
78
|
+
Notes:
|
|
79
|
+
- Programmatic overriding of TLS 1.3 ciphersuites requires a recent OpenSSL + pyOpenSSL exposing `set_ciphersuites`.
|
|
80
|
+
- Cipher probing may produce handshake failures for many ciphers — the tool records successes and errors.
|
|
81
|
+
- Only use pqcprobe against systems you own or are authorized to test.
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Security
|
|
86
|
+
issues should be reported privately per [SECURITY.md](SECURITY.md).
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
[MIT](LICENSE) © Andre Van Klaveren
|
|
91
|
+
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Decision Log
|
|
2
|
+
|
|
3
|
+
A running record of notable decisions for pqcprobe — the *why* behind choices
|
|
4
|
+
that the code and commit history don't make obvious on their own. Newest entries
|
|
5
|
+
at the bottom. Each entry: context, the decision, and its status.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0001 — Purpose: post-quantum readiness auditing (2026-07-03)
|
|
10
|
+
|
|
11
|
+
**Context.** The tool is used to audit our own systems in preparation for
|
|
12
|
+
post-quantum cryptography risk, specifically "harvest-now, decrypt-later"
|
|
13
|
+
(HNDL) exposure.
|
|
14
|
+
|
|
15
|
+
**Decision.** Treat the TLS **key-exchange group** as the primary signal, not
|
|
16
|
+
the symmetric cipher. HNDL risk lives in the key exchange: a session whose keys
|
|
17
|
+
are agreed with classical-only ECDHE can be recorded today and decrypted once a
|
|
18
|
+
cryptographically relevant quantum computer exists. The tool's reporting and
|
|
19
|
+
future features are prioritized accordingly.
|
|
20
|
+
|
|
21
|
+
**Status.** Accepted.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 0005 — Dependency and platform baseline (2026-07-03)
|
|
26
|
+
|
|
27
|
+
**Context.** Dependencies were pinned to older releases, and the CI matrix
|
|
28
|
+
tested Python 3.8 — which neither pinned dependency supports (both require
|
|
29
|
+
Python >= 3.9), so the 3.8 job could never install.
|
|
30
|
+
|
|
31
|
+
**Decision.** Baseline is Python 3.9+ with pyOpenSSL 26.3.0 and cryptography
|
|
32
|
+
49.0.0 (latest stable as of this date). OpenSSL 3.5+ is recommended because it
|
|
33
|
+
ships native ML-KEM and enables the hybrid `X25519MLKEM768` group by default,
|
|
34
|
+
which the PQC work depends on. CI tests Python 3.9–3.13.
|
|
35
|
+
|
|
36
|
+
**Status.** Accepted.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 0006 — PQC group probing approach (2026-07-03)
|
|
41
|
+
|
|
42
|
+
**Context.** For PQC auditing the tool needs to (a) report the group actually
|
|
43
|
+
negotiated and (b) enumerate which groups a server will accept. We evaluated
|
|
44
|
+
pyOpenSSL 26.3.0's API for this.
|
|
45
|
+
|
|
46
|
+
**Findings.**
|
|
47
|
+
- `Connection.get_group_name()` **is** available — reading the negotiated group
|
|
48
|
+
(e.g. `X25519MLKEM768` vs. a classical `x25519`) is a clean pure-Python call.
|
|
49
|
+
- `set_groups` / `SSL_CTX_set1_groups_list` is **not** bound in pyOpenSSL
|
|
50
|
+
26.3.0, so we cannot force a specific group list from Python to enumerate
|
|
51
|
+
server support.
|
|
52
|
+
|
|
53
|
+
**Decision.** Report the negotiated group via `get_group_name()`. Enumerate
|
|
54
|
+
server support by shelling out to the native `openssl s_client -groups <group>`
|
|
55
|
+
(OpenSSL 3.5.x supports all ML-KEM hybrids). Flag any server offering only
|
|
56
|
+
classical key exchange as an HNDL risk.
|
|
57
|
+
|
|
58
|
+
**Status.** Implemented on `feature/pqc-group-probing`. Notes from
|
|
59
|
+
implementation: openssl only prints the `Negotiated TLS1.3 group:` line for
|
|
60
|
+
some groups, so handshake success is detected via a real negotiated cipher and
|
|
61
|
+
the forced group is recorded as the one used. Groups the local openssl does not
|
|
62
|
+
recognize are reported as `unknown_locally` (not `unsupported`). Added
|
|
63
|
+
`--fail-on-classical-only` (exit 3) for audit pipelines and `--no-groups` to
|
|
64
|
+
skip probing when the openssl CLI is unavailable.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 0007 — Project name: pqcprobe (2026-07-03)
|
|
69
|
+
|
|
70
|
+
**Context.** Before publishing a public GitHub repo we checked the working names
|
|
71
|
+
for conflicts. `tlsprobe` collides with an archived C tool
|
|
72
|
+
(github.com/marcobellaccini/tlsprobe) and sits near `tls_prober` / `tlsprober`;
|
|
73
|
+
`TLS-Audit`/`tlsaudit` collides with an active, feature-similar Go tool
|
|
74
|
+
(github.com/adedayo/tlsaudit). Both original names are generic and neither
|
|
75
|
+
signals the post-quantum focus that differentiates this tool.
|
|
76
|
+
|
|
77
|
+
**Decision.** Rename the project and CLI to **pqcprobe** ("PQC probe"). It is
|
|
78
|
+
unused on PyPI and GitHub and elsewhere on the web, reads clearly, and escapes
|
|
79
|
+
the crowded generic TLS-scanner namespace. Renamed the module
|
|
80
|
+
(`tlsprobe.py` -> `pqcprobe.py`), test file, and all references.
|
|
81
|
+
|
|
82
|
+
**Follow-up.** The local working directory is still `TLS-Audit`; name the public
|
|
83
|
+
GitHub repo `pqcprobe` at creation for consistency. The PyPI name `pqcprobe` is
|
|
84
|
+
available if we later publish.
|
|
85
|
+
|
|
86
|
+
**Status.** Accepted.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 0008 — Open-source and packaging setup (2026-07-03)
|
|
91
|
+
|
|
92
|
+
**Context.** Preparing the project to accept contributions and be published to
|
|
93
|
+
PyPI. It previously had no license file, contributor docs, or packaging.
|
|
94
|
+
|
|
95
|
+
**Decisions.**
|
|
96
|
+
- **License:** MIT, © 2026 Andre Van Klaveren (added a real `LICENSE` file to
|
|
97
|
+
back the README's existing claim).
|
|
98
|
+
- **Governance:** added `CONTRIBUTING.md`, `SECURITY.md` (private disclosure to
|
|
99
|
+
andre@vanklaverens.com — important for a security tool), `CODE_OF_CONDUCT.md`
|
|
100
|
+
(Contributor Covenant 2.1), and `CHANGELOG.md` (Keep a Changelog).
|
|
101
|
+
- **Packaging:** `pyproject.toml` with the hatchling backend; version is a
|
|
102
|
+
single source of truth read from `__version__` in `pqcprobe.py`; kept the flat
|
|
103
|
+
single-module layout (packaged via `only-include`) rather than moving to
|
|
104
|
+
`src/`, to minimize churn. Exposes a `pqcprobe` console entry point
|
|
105
|
+
(`pqcprobe:main`, which already returned an exit code).
|
|
106
|
+
- **Publishing:** GitHub Actions workflow using PyPI Trusted Publishing (OIDC),
|
|
107
|
+
triggered on a published GitHub Release — no API tokens stored. The actual
|
|
108
|
+
upload is a human-triggered release, never automated blindly.
|
|
109
|
+
|
|
110
|
+
**Follow-ups before first publish.**
|
|
111
|
+
- GitHub namespace is `opratr`; project URLs in `pyproject.toml` and
|
|
112
|
+
`CHANGELOG.md` are set accordingly.
|
|
113
|
+
- Register a PyPI "pending" trusted publisher for project `pqcprobe`
|
|
114
|
+
(workflow `publish.yml`, environment `pypi`).
|
|
115
|
+
|
|
116
|
+
**Status.** Accepted; build validated locally (`python -m build` + `twine
|
|
117
|
+
check` pass, wheel installs and the `pqcprobe` command runs). Not yet uploaded.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 0009 — Linting and security scanning (2026-07-03)
|
|
122
|
+
|
|
123
|
+
**Context.** The project had no linting or static/security analysis — the only
|
|
124
|
+
automated gate was the unit tests. For an OSS security tool that shells out to
|
|
125
|
+
`openssl` and parses untrusted network data, that is a gap.
|
|
126
|
+
|
|
127
|
+
**Decisions.**
|
|
128
|
+
- **Ruff** for linting (rules E/F/W/I/UP), configured in `pyproject.toml` and
|
|
129
|
+
enforced in CI via `ruff check`. `E501` (line length) is left to `ruff format`,
|
|
130
|
+
which is *not* gated — the code is not yet reformatted repo-wide, and a full
|
|
131
|
+
reformat was deliberately avoided to keep diffs reviewable. Import-ordering
|
|
132
|
+
autofixes were applied.
|
|
133
|
+
- **Bandit** for Python SAST, gated in CI at **medium+** severity. The current
|
|
134
|
+
code produces only low-severity findings: `B404` (subprocess import) and ~25
|
|
135
|
+
`B110` (try/except/pass). These are accepted as known/architectural — the
|
|
136
|
+
subprocess call is core functionality (annotated with a safety rationale and
|
|
137
|
+
`# noqa: S603`), and the broad excepts are pre-existing tech debt already
|
|
138
|
+
noted in the original review (candidate for a later cleanup). Gating at
|
|
139
|
+
medium+ keeps CI honest without failing on these.
|
|
140
|
+
- **pip-audit** scans runtime dependencies (`requirements.txt`) for CVEs in CI.
|
|
141
|
+
- **CodeQL** (`github/codeql-action@v4`, `security-and-quality` queries) runs on
|
|
142
|
+
push/PR and weekly.
|
|
143
|
+
- **Dependabot** for the `pip` and `github-actions` ecosystems (weekly), which
|
|
144
|
+
also keeps our "use current versions" preference on autopilot.
|
|
145
|
+
- **pre-commit** config for local Ruff + hygiene hooks (opt-in, not CI-gating).
|
|
146
|
+
- README shows status badges for Tests, Lint & Security, and CodeQL.
|
|
147
|
+
|
|
148
|
+
**Follow-ups.**
|
|
149
|
+
- README badge URLs use the `opratr` namespace; they render once the repo is
|
|
150
|
+
pushed to GitHub.
|
|
151
|
+
- Optional future work: reduce the `B110` broad-except count and then consider
|
|
152
|
+
applying `ruff format` repo-wide and gating it.
|
|
153
|
+
|
|
154
|
+
**Status.** Accepted; all checks pass locally (ruff clean, bandit medium+ clean,
|
|
155
|
+
pip-audit clean, 21 tests pass).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 0010 — Hash-pinned runtime dependencies (2026-07-04)
|
|
160
|
+
|
|
161
|
+
**Context.** For supply-chain integrity we want installs to verify the exact
|
|
162
|
+
artifact, not just the version — protecting against a compromised or substituted
|
|
163
|
+
package on the index.
|
|
164
|
+
|
|
165
|
+
**Decisions.**
|
|
166
|
+
- Hashes live in a **lockfile**, not in package metadata. `pyproject.toml`
|
|
167
|
+
keeps *abstract* version constraints (what `pip install pqcprobe` resolves for
|
|
168
|
+
end users); `requirements.txt` is a fully pinned, hash-locked lockfile of the
|
|
169
|
+
entire transitive tree (cryptography, cffi, pycparser, pyOpenSSL,
|
|
170
|
+
typing-extensions).
|
|
171
|
+
- The lockfile is generated from `requirements.in` with
|
|
172
|
+
`uv pip compile --universal --generate-hashes` — `--universal` embeds
|
|
173
|
+
environment markers so one file is correct across Python 3.9–3.13 (e.g.
|
|
174
|
+
`pycparser` 2.23 for <3.10 vs 3.0 for ≥3.10; `typing-extensions` only <3.13).
|
|
175
|
+
- CI's test job installs with `pip install --require-hashes -r requirements.txt`,
|
|
176
|
+
which fails the build if any artifact's hash does not match (verified locally:
|
|
177
|
+
a tampered hash produces "THESE PACKAGES DO NOT MATCH THE HASHES"). Tests run
|
|
178
|
+
without an editable install because `pqcprobe.py` is importable from the repo
|
|
179
|
+
root; the packaged-install path is validated separately in `publish.yml`.
|
|
180
|
+
- **`requires-python` tightened to `>=3.9.2`**: surfaced during resolution,
|
|
181
|
+
cryptography 49 excludes Python 3.9.0/3.9.1, so the previous `>=3.9` was
|
|
182
|
+
inaccurate.
|
|
183
|
+
|
|
184
|
+
**Notes / limits.**
|
|
185
|
+
- Dev tools and the build backend were initially unpinned; now addressed in
|
|
186
|
+
[[0011]].
|
|
187
|
+
- Dependabot understands hashed requirements and will regenerate hashes on
|
|
188
|
+
updates, so this stays compatible with the "keep versions current" policy.
|
|
189
|
+
|
|
190
|
+
**Status.** Accepted; hash-verified install confirmed in a clean venv, tests
|
|
191
|
+
pass.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 0011 — Hash-pin dev tools and the build backend (2026-07-04)
|
|
196
|
+
|
|
197
|
+
**Context.** Extends [[0010]] to close the residual unpinned surface: the CI dev
|
|
198
|
+
tools (ruff, bandit, pip-audit, build, twine, pre-commit) and the build backend
|
|
199
|
+
(hatchling), which `python -m build` otherwise fetches unpinned in an isolated
|
|
200
|
+
environment.
|
|
201
|
+
|
|
202
|
+
**Decisions.**
|
|
203
|
+
- Added `requirements-dev.in` -> `requirements-dev.txt`, a universal
|
|
204
|
+
hash-locked lockfile for the dev/CI toolchain (same `uv pip compile` flow as
|
|
205
|
+
the runtime lockfile).
|
|
206
|
+
- `lint.yml` installs the tools with `--require-hashes -r requirements-dev.txt`
|
|
207
|
+
instead of an unpinned `pip install`.
|
|
208
|
+
- `publish.yml` installs the same pinned set and runs `python -m build
|
|
209
|
+
--no-isolation`, so the build uses the hash-pinned hatchling rather than
|
|
210
|
+
fetching a build backend on the fly.
|
|
211
|
+
- `hatchling` is included in the dev lockfile specifically to make the
|
|
212
|
+
no-isolation build verifiable.
|
|
213
|
+
|
|
214
|
+
**Notes.**
|
|
215
|
+
- Universal resolution pins per-Python variants where a tool dropped an older
|
|
216
|
+
interpreter (e.g. `bandit` 1.8.6 on 3.9 vs 1.9.4 on ≥3.10); CI (3.13) gets the
|
|
217
|
+
current releases.
|
|
218
|
+
- pre-commit hook repos remain pinned by git `rev` (their native mechanism),
|
|
219
|
+
which is separate from the pip hash lockfiles.
|
|
220
|
+
|
|
221
|
+
**Status.** Accepted; verified in a clean venv — pinned tools run (ruff, bandit,
|
|
222
|
+
pip-audit clean) and `build --no-isolation` + `twine check` pass.
|