runspec-webops-core 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/.gitignore +5 -0
  2. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/CHANGELOG.md +15 -0
  3. runspec_webops_core-0.3.2/PKG-INFO +56 -0
  4. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/README.md +1 -1
  5. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/pyproject.toml +2 -1
  6. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/tls.py +10 -4
  7. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_tls.py +27 -0
  8. runspec_webops_core-0.3.0/PKG-INFO +0 -13
  9. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/__init__.py +0 -0
  10. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/dns.py +0 -0
  11. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/errors.py +0 -0
  12. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/http.py +0 -0
  13. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/openapi.py +0 -0
  14. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/runspec_webops_core/proxy.py +0 -0
  15. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/__init__.py +0 -0
  16. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_dns.py +0 -0
  17. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_http.py +0 -0
  18. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_openapi.py +0 -0
  19. {runspec_webops_core-0.3.0 → runspec_webops_core-0.3.2}/tests/test_proxy.py +0 -0
@@ -25,6 +25,8 @@ coverage.xml
25
25
  # Node
26
26
  node_modules/
27
27
  dist/
28
+ # runspec manifest output (written at npm pack time)
29
+ runspec-files.json
28
30
  *.js.map
29
31
  .npm
30
32
 
@@ -59,3 +61,6 @@ chainlit-docs/
59
61
 
60
62
  # Stray committed test venv (removed from tracking)
61
63
  .venv-test/
64
+
65
+ # Private gitleaks rules (see .gitleaks.toml)
66
+ .gitleaks.local.toml
@@ -1,5 +1,20 @@
1
1
  # runspec-webops-core Changelog
2
2
 
3
+ ## [0.3.2] — 2026-11-12
4
+
5
+ ### Fixed
6
+
7
+ - `connect_peer` (behind `cert-check`, `cert-chain` and `tls-info`) failed on
8
+ Python 3.13+ with `'bytes' object has no attribute 'public_bytes'`: the public
9
+ `SSLSocket.get_unverified_chain()` returns DER bytes, not certificate objects.
10
+ Both shapes are read now.
11
+
12
+ ## [0.3.1] — 2026-11-05
13
+
14
+ ### Fixed
15
+
16
+ - The PyPI page shows the README (`pyproject.toml` didn't declare it).
17
+
3
18
  ## [0.3.0] — 2026-09-01
4
19
 
5
20
  ### Added — custom CA bundle for TLS verification
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.5
2
+ Name: runspec-webops-core
3
+ Version: 0.3.2
4
+ Summary: Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable core behind the runspec web runnables (no runspec dependency, no runnables)
5
+ Requires-Python: >=3.10
6
+ Provides-Extra: dev
7
+ Requires-Dist: mypy; extra == 'dev'
8
+ Requires-Dist: pytest>=8.0; extra == 'dev'
9
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
10
+ Requires-Dist: ruff==0.15.20; extra == 'dev'
11
+ Requires-Dist: types-pyyaml; extra == 'dev'
12
+ Provides-Extra: yaml
13
+ Requires-Dist: pyyaml>=6.0; extra == 'yaml'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # runspec-webops-core
17
+
18
+ Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable logic
19
+ core behind the cross-platform web runnables in
20
+ [`runspec-linux`](https://pypi.org/project/runspec-linux/) and [`runspec-windows`](https://pypi.org/project/runspec-windows/).
21
+
22
+ Stdlib only (`ssl` / `socket` / `http.client`) — **no `runspec` dependency, no
23
+ `runspec.toml`, and no entry points**, so installing it exposes the helper
24
+ functions for import without surfacing any runnables (it is invisible to
25
+ `runspec local` / `runspec serve` discovery).
26
+
27
+ ```python
28
+ from runspec_webops_core import connect_peer, leaf_report, probe, resolve
29
+
30
+ peer = connect_peer("example.com", 443, 10)
31
+ print(leaf_report(peer, warn_days=30)) # days-to-expiry, issuer, SANs, trust
32
+ print(probe("https://example.com", 10)) # status, timing, redirect target
33
+ print(resolve("example.com", want_reverse=True))
34
+ ```
35
+
36
+ | Function | What it does |
37
+ |---|---|
38
+ | `connect_peer` / `leaf_report` / `chain_report` | TLS certificate inspection — expiry, issuer, SANs, fingerprint, full chain, trust result |
39
+ | `probe` / `classify` | Single HTTP(S) request — status, response time, redirect target |
40
+ | `fetch_headers` | Response headers + a security-header report (HSTS, CSP, …) |
41
+ | `trace_redirects` | Walk the redirect chain, hop by hop, with loop detection |
42
+ | `resolve` | Forward (A/AAAA) and reverse (PTR) DNS lookups |
43
+ | `load_document` / `summarize_spec` / `describe_operation` | Read an OpenAPI/Swagger spec (URL or file) and condense its shape — operations, params, request/response schemas — to bounded JSON |
44
+
45
+ OpenAPI/Swagger specs in JSON parse with the stdlib; YAML needs the optional
46
+ `yaml` extra (`pip install runspec-webops-core[yaml]`).
47
+
48
+ Each function returns plain data and *raises* on failure (`ConnectError`,
49
+ `ResolveError` — both subclasses of `WebopsCoreError`).
50
+
51
+ ## Corporate wrapping
52
+
53
+ Depend on `runspec-webops-core` alone, import the helpers, and ship your own
54
+ private package with its own `runspec.toml` that bakes in corporate defaults
55
+ (proxies, allowed hosts, redaction) as plain params — only your wrapped
56
+ runnables surface in the venv, the generic published code stays clean.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable logic
4
4
  core behind the cross-platform web runnables in
5
- [`runspec-linux`](../runspec-linux) and [`runspec-windows`](../runspec-windows).
5
+ [`runspec-linux`](https://pypi.org/project/runspec-linux/) and [`runspec-windows`](https://pypi.org/project/runspec-windows/).
6
6
 
7
7
  Stdlib only (`ssl` / `socket` / `http.client`) — **no `runspec` dependency, no
8
8
  `runspec.toml`, and no entry points**, so installing it exposes the helper
@@ -4,9 +4,10 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runspec-webops-core"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  requires-python = ">=3.10"
9
9
  description = "Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable core behind the runspec web runnables (no runspec dependency, no runnables)"
10
+ readme = "README.md"
10
11
  dependencies = []
11
12
 
12
13
  [project.optional-dependencies]
@@ -201,8 +201,8 @@ def client_ssl_context(insecure: bool = False, ca_bundle: str | None = None) ->
201
201
  def _peer_chain(ssock: ssl.SSLSocket) -> list[CertInfo]:
202
202
  """Return the server-presented certificate chain (leaf first).
203
203
 
204
- Prefers the public ``SSLSocket.get_unverified_chain()`` (Python 3.13+),
205
- falls back to the underlying ``_sslobj`` method (3.10–3.12), and finally to
204
+ Prefers the public ``SSLSocket.get_unverified_chain()`` (Python 3.13+,
205
+ which returns DER bytes), falls back to the underlying ``_sslobj`` method (3.10–3.12), and finally to
206
206
  the leaf-only certificate via ``getpeercert`` when neither is available.
207
207
  """
208
208
  getter = getattr(ssock, "get_unverified_chain", None)
@@ -218,8 +218,14 @@ def _peer_chain(ssock: ssl.SSLSocket) -> list[CertInfo]:
218
218
  if raw:
219
219
  out: list[CertInfo] = []
220
220
  for cert in raw:
221
- der = ssl.PEM_cert_to_DER_cert(cert.public_bytes())
222
- out.append(cert_info_from_ssl(cert.get_info(), der))
221
+ if isinstance(cert, (bytes, bytearray)):
222
+ # Python 3.13+ public API: DER-encoded bytes.
223
+ der = bytes(cert)
224
+ out.append(cert_info_from_ssl(_decode_der(der), der))
225
+ else:
226
+ # 3.10–3.12 ``_sslobj`` method: ``_ssl.Certificate`` objects.
227
+ der = ssl.PEM_cert_to_DER_cert(cert.public_bytes())
228
+ out.append(cert_info_from_ssl(cert.get_info(), der))
223
229
  return out
224
230
 
225
231
  # Fallback: only the leaf is recoverable on this interpreter.
@@ -132,3 +132,30 @@ def test_chain_report_marks_root() -> None:
132
132
  rep = chain_report(peer, now=NOW)
133
133
  assert [link["self_signed"] for link in rep] == [False, True]
134
134
  assert rep[0]["days_remaining"] == 30
135
+
136
+
137
+ def _a_der_cert() -> bytes:
138
+ """Any real DER certificate: the first system CA (no network needed)."""
139
+ certs = ssl.create_default_context().get_ca_certs(binary_form=True)
140
+ if not certs:
141
+ pytest.skip("no system CA certificates to use as a sample")
142
+ return certs[0]
143
+
144
+
145
+ def test_peer_chain_reads_der_bytes_from_public_api() -> None:
146
+ # Python 3.13+ ``SSLSocket.get_unverified_chain()`` returns DER bytes, not
147
+ # ``_ssl.Certificate`` objects; this used to fail with
148
+ # "'bytes' object has no attribute 'public_bytes'".
149
+ from runspec_webops_core.tls import _peer_chain
150
+
151
+ der = _a_der_cert()
152
+
153
+ class FakeSocket:
154
+ def get_unverified_chain(self) -> list[bytes]:
155
+ return [der, der]
156
+
157
+ chain = _peer_chain(FakeSocket()) # type: ignore[arg-type]
158
+ assert len(chain) == 2
159
+ assert chain[0].subject
160
+ assert chain[0].valid_to
161
+ assert chain[0].fingerprint_sha256
@@ -1,13 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: runspec-webops-core
3
- Version: 0.3.0
4
- Summary: Pure-Python HTTPS/TLS certificate + web-endpoint helpers — the importable core behind the runspec web runnables (no runspec dependency, no runnables)
5
- Requires-Python: >=3.10
6
- Provides-Extra: dev
7
- Requires-Dist: mypy; extra == 'dev'
8
- Requires-Dist: pytest>=8.0; extra == 'dev'
9
- Requires-Dist: pyyaml>=6.0; extra == 'dev'
10
- Requires-Dist: ruff==0.15.20; extra == 'dev'
11
- Requires-Dist: types-pyyaml; extra == 'dev'
12
- Provides-Extra: yaml
13
- Requires-Dist: pyyaml>=6.0; extra == 'yaml'