Skip to content

feat(verify): let the reachability probe and live smoke speak TLS (BACKLOG #1178) - #914

Open
wshallwshall wants to merge 2 commits into
mainfrom
claude/mgr-1178
Open

feat(verify): let the reachability probe and live smoke speak TLS (BACKLOG #1178)#914
wshallwshall wants to merge 2 commits into
mainfrom
claude/mgr-1178

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

BACKLOG #1178 -- the reachability probe and the live smoke can now cross the hop they test

This PR does NOT close #1178 and does not claim to. The item carries Verdict: research and
Closing-act: scorecard-rescore, which no builder performs. Its banner is untouched. Two named
residual limbs are built; the rest of the item stands.

Scope is the item's own "Proposed work" list, the two entries that live in one file pair:
pass the destination's SSL context into probe_tcp_reachable, and grade the second bare
connection in verify/smoke.py.

Re-measurement first, by execution, with a positive control in the same run

Measured at c57903c2c before any edit. Both named defects held, and one of the item's two
anchors had drifted.

Check Result
inspect.signature(probe_tcp_reachable) (host, port, timeout, label). No ssl or tls parameter of any kind.
Item's anchor transports/base.py:557 DRIFTED. The function is at :588, the bare connect at :593. Corrected in the row.
Item's anchor verify/smoke.py:126 Lands exactly.
tls=true MLLP destination builds a context? Yes, a real ssl.SSLContext.
Its test_connection() Returned OK. A recording listener saw a connection carrying zero bytes and no ClientHello.
Positive control: same destination's send(), same listener, same run Sent a real ClientHello (16 03 01 ...). So the instrument could see a handshake, and the zero-byte reading is a finding rather than a blind probe.
smoke_live Wrote \x0bMSH|^~\&|... onto a bare socket. A synthetic body, but a body, and worse than the probe on this cell's terms.

The item pre-rejects "it sends zero bytes so nothing is disclosed" as the vacuity argument. I did
not make it, and the smoke limb turns out not to qualify for it at all.

What changed

  • probe_tcp_reachable takes optional ssl_context and server_hostname (defaulting to host)
    and hands them to asyncio.open_connection. It stays a no-data probe: under TLS the handshake is
    all that crosses. A handshake failure is now named as one rather than reported as a failed
    connect, because a trust-anchor problem read as an unreachable partner sends the operator to the
    firewall. None keeps today's plaintext probe byte-identical, which is the honest posture for raw
    TCP and X12: they have no tls parameter to migrate to and their cleartext hop is gated
    separately at construction.
  • smoke_live takes the same optional context and completes the handshake before any application
    byte. Reachable as messagefoundry verify --smoke live --smoke-tls [--smoke-tls-ca <file>].
  • live_smoke_ssl_context builds that client context through the shared hardening seam
    (harden_kex_groups / harden_cipher_suites / harden_verify_flags) rather than inheriting the
    interpreter's defaults, and carries no verify-off switch. A smoke that accepts any certificate
    proves the port answers, not that it is the engine.
  • Neither site ever probes one protocol and retries the other. That fall-back is what ASVS 12.3.1
    forbids. The plaintext smoke's "0 bytes received" now names a TLS inbound as a possible cause,
    which is a diagnosis and deliberately not a retry.
  • Both new TLS call sites are registered in scripts/security/crypto_inventory_check.py. The gate
    was made to fail on purpose first (it reported both files undocumented), then satisfied: 77 sites
    before, 79 after.

What is deliberately NOT here

The MLLP destination's call site, transports/mllp.py:1215, still passes no context. It is a
one-line change and needs no design work, but a peer builder held mllp.py in this wave and my
brief forbids editing a sibling's file. Until it lands, a deploying site's connection test for a
tls=true MLLP destination would still open a plaintext socket to the partner. This is the
remaining half of the item's probe residual and should be dispatched.
tcp.py and x12.py need
no such change: they have no TLS to pass.

Everything else in the item is untouched: the gradient amendment, the attested early return at both
sites, the attestation re-scoping, the remaining data-layer hop sites, the upstream-termination
allow, and the Vault and IDE limbs.

Checks

Run from this worktree with .venv\Scripts\python.exe throughout. Interpreter and pins printed
together, since three interpreters on this machine disagree:

sys.executable   ...\worktrees\manager-277cbb-b1178\.venv\Scripts\python.exe
python           3.14.6
OpenSSL          OpenSSL 3.5.7 9 Jun 2026
cryptography     50.0.1      requirements.lock pins cryptography==50.0.1  (agree)

Ran, all green:

  • ruff check . and ruff format --check .
  • mypy messagefoundry (strict): no issues in 268 source files
  • scripts/security/crypto_inventory_check.py: OK, no drift
  • scripts/docs/backlog_status_check.py: OK, 670 items, each declaring one status
  • pytest tests/test_verify.py tests/test_connection_api.py (80 passed)
  • pytest tests/test_tls_policy.py tests/test_tls_cipher_assertion_sites.py tests/test_cli.py (240 passed)
  • pytest tests/test_mllp_tls.py tests/test_transports.py tests/test_tcp_transport.py tests/test_hop_refusal_rawtcp.py tests/test_mllp_persistent.py (220 passed, 1 skipped)
  • pre-commit at commit time, all hooks. One real catch: a stray vertical-tab byte the control-char
    hook found in my BACKLOG text, from the quoted-heredoc unescaping bug. Fixed, then green.

Skipped: the full suite. Under fleet contention (ten builders on this account) it may not finish
inside one turn. I ran the files covering the change plus the TLS and CLI neighbours instead.

CI legs somebody must read after I exit: the full test matrix including py3.11, bandit,
pip-audit, and the crypto-inventory (ASVS 11.1.3 discovery gate) leg in security.yml, which
this PR moves. Also anything that only runs on a hosted runner, such as windows-service-smoke.

New tests

  • tests/test_connection_api.py::test_probe_sends_a_client_hello_only_when_given_a_context is the
    direct regression, built the same paired way as the re-measurement: one recorder, both arms, so
    the plaintext arm proves the recorder can see bytes at all.
  • tests/test_verify.py::test_live_smoke_over_tls_puts_no_plaintext_frame_on_the_wire is the same
    shape for the smoke, and asserts no MSH escapes the handshake.
  • Plus: handshake-failure naming at both sites, the TLS smoke passing on AA, the cleartext
    diagnostic wording, the context having no verify-off escape, and a forward-secrecy assertion test
    that monkeypatches _is_forward_secret so the shared assertion can actually fire.

Housekeeping

  • Ultracode: off. No system-reminder in this session declared it, which is the whole test.
  • No sibling-owned file is touched. No reviewed label, no enqueue, no auto-merge.

…CKLOG #1178)

probe_tcp_reachable and smoke_live both opened a bare socket regardless
of the hop they tested. Re-measured at c57903c by execution: a tls=true
MLLP destination's test_connection() returned OK having sent no
ClientHello, while the same destination's send() in the same run sent
one -- the zero-byte reading was a finding, not a blind probe. smoke_live
wrote a whole MLLP frame onto a plaintext socket.

Both now take an optional ssl_context and cross the hop the caller uses.
None keeps the plaintext path byte-identical, which is right for raw TCP
and X12. A handshake failure is named as one, not as a failed connect.
Neither retries the other protocol: that fall-back is what ASVS 12.3.1
forbids. The smoke is reachable as verify --smoke live --smoke-tls; its
context uses the shared hardening seam and has no verify-off switch.
Both new TLS call sites are registered in the crypto inventory.

Corrects the item's drifted anchor: base.py:588, not :557. Item stays
OPEN; the mllp.py call site is still owed.
@github-actions github-actions Bot added the ci-red A required check went red. Attribute it before retrying. label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-red A required check went red. Attribute it before retrying.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant