Skip to content

security: hardening pass — TLS hostname verification, async UAF fixes, secret zeroization, input validation - #7

Merged
jsulmont merged 5 commits into
mainfrom
security/hardening-2026-10
Oct 6, 2026
Merged

jsulmont merged 5 commits into
mainfrom
security/hardening-2026-10

Conversation

@jsulmont

@jsulmont jsulmont commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Summary

A focused security hardening pass across five commits. No public API removals; one default flip (TLS hostname verification on) and one removed committed credential file are deliberate behavior changes, called out below.

7cb0581 — TLS hostname verification, async-context UAFs, bounded input

  • CWE-297: Paho SSL verify (hostname check) was never set, so any CA-signed certificate could impersonate the broker. New TlsOptions::verify_hostname, default true — deployments pointing at certs whose SAN/CN don't match the connect host will now fail the handshake until they fix the cert or opt out explicitly.
  • CWE-416: heap-own Paho response contexts (AsyncOp) so late callbacks after wait_for() timeouts cannot touch destroyed stack promises; rebind MQTT callbacks in move ctor/assign and reset shallow-copied ssl_opts_; reuse the client handle across reconnects so in-flight publishes cannot hold a destroyed handle (CWE-367).
  • CWE-362: copy config callbacks/caps under mutex in on_message_arrived; user callbacks never invoked under a lock.
  • CWE-20/400: strict JSON parser for STATE online; oversized inbound payloads dropped via Config::max_payload_bytes.
  • CWE-248/117/319: exception barriers around user callback dispatch; sanitized log messages; warning on credentials over plaintext transport.

a51d658 — topic validation in Topic::parse

Rejects empty/oversized group/edge-node/device ids (>128 chars), device ids on node-scoped messages, missing device ids on device-scoped messages, and trailing components that were previously silently ignored. These strings become permanent host-state keys and log content (CWE-400, CWE-117). Adds 56 lines of cases to tests/test_topic.cpp.

880cec6 — C API exception barriers and narrowing guards

  • All publisher/payload extern "C" functions wrapped in try/catch so C++ exceptions cannot unwind through the C boundary (CWE-248).
  • Lengths > INT_MAX rejected before static_cast<int> narrowing into protobuf parse/serialize calls (CWE-190).
  • Header contracts corrected: payload handles are not thread-safe; destroy() must not race other calls; returned string pointers are invalidated by mutation.

00e5ece — committed credentials removed, examples and TLS docs hardened

  • certs/passwordfile (admin/admin hash) removed — CI regenerates it; anyone depending on the committed file must adjust.
  • Auth/TCK examples read MQTT_USERNAME/MQTT_PASSWORD env vars; warn when --password is passed via argv (visible in process listings).
  • Mimic examples warn that the public plaintext broker is world-readable.
  • TLS_SETUP.md: dropped --insecure from the verification snippet, fixed stale API/example names; README.md recommends ssl:// with a trust store in production.

75c980e — secret zeroization

MQTT password and TLS key passphrase are adopted from Config into detail::SecureBuffer members that scrub storage on clear, move, and destruction; the source std::string copies are zeroed immediately with volatile writes (immune to dead-store elimination) so plaintext no longer lingers on the heap (CWE-316). Paho retains its own copy for the connection lifetime — outside the library's control.

Breaking / behavioral changes

  • TLS hostname verification now defaults to on (CWE-297 fix). Misconfigured hosts fail the handshake; opt out via TlsOptions::verify_hostname = false.
  • Committed certs/passwordfile removed (CI regenerates).
  • Malformed topics that were previously accepted-with-surprises (trailing components, oversized ids, scope/id mismatches) are now rejected.

Test plan

  • Full suite on x86_64 and ARM64 CI (release workflows build both)
  • tests/test_topic.cpp — new validation cases
  • Manual: TLS connect against a cert with mismatched hostname now fails; matching hostname + explicit opt-out both still work
  • Manual: auth examples run with env-var credentials

…ound untrusted input

- Set Paho SSL 'verify' (hostname check) from new TlsOptions::verify_hostname
  (default true); previously never set, so any CA-signed cert impersonated
  the broker (CWE-297)
- Heap-own Paho response contexts (AsyncOp) so late callbacks after
  wait_for() timeouts cannot touch destroyed stack promises (CWE-416);
  delete on synchronous failure
- Rebind MQTT callbacks in move ctor/assign; reset shallow-copied ssl_opts_
  (CWE-416)
- Reuse the client handle across reconnects so in-flight publishes cannot
  hold a destroyed handle (CWE-367)
- Copy config callbacks/caps under mutex in on_message_arrived; never invoke
  user callbacks under a lock (CWE-362)
- Strict JSON parser for STATE online; drop oversized inbound payloads via
  Config::max_payload_bytes (CWE-20, CWE-400)
- Exception barriers around user callback dispatch; sanitize log messages
  (CWE-248, CWE-117); warn on credentials over plaintext transport (CWE-319)
Reject empty/oversized group/edge-node/device ids (>128 chars), device ids
on node-scoped messages, missing device ids on device-scoped messages, and
trailing components (previously silently ignored). These strings become
permanent host-state keys and log content (CWE-400, CWE-117).
- Wrap all publisher/payload extern "C" functions in try/catch so C++
  exceptions cannot unwind through the C boundary (CWE-248)
- Reject lengths > INT_MAX before static_cast<int> narrowing into protobuf
  parse/serialize calls (CWE-190)
- Fix header docs: payload handles are not thread-safe; destroy() must not
  race other calls; returned string pointers are invalidated by mutation
- Remove certs/passwordfile (admin/admin hash; CI regenerates it)
- Auth/TCK examples read MQTT_USERNAME/MQTT_PASSWORD env vars; warn when
  --password is passed via argv (visible in process listings)
- Mimic examples warn that the public plaintext broker is world-readable
- TLS_SETUP.md: drop --insecure from the verification snippet, fix stale
  API/example names; README: recommend ssl:// with trust store in production
Secrets are adopted from Config into detail::SecureBuffer members that
scrub their storage on clear, move, and destruction; the source
std::string copies are zeroed immediately (volatile writes, immune to
dead-store elimination) so plaintext no longer lingers in the heap for
the object's lifetime or after frees (CWE-316). Public API unchanged;
Paho retains its own copy for the connection lifetime, which is outside
the library's control.
@gitguardian

gitguardian Bot commented Oct 6, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 6 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
37912030 Triggered Generic Password 75c980e src/host_application.cpp View secret
37912029 Triggered Generic Password 00e5ece examples/test_auth_password.cpp View secret
37912028 Triggered Generic Password 00e5ece examples/tck_host_application_main.cpp View secret
37912029 Triggered Generic Password 00e5ece examples/test_auth_combined.cpp View secret
37912028 Triggered Generic Password 00e5ece examples/tck_edge_node_main.cpp View secret
37912030 Triggered Generic Password 75c980e src/edge_node.cpp View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@jsulmont
jsulmont merged commit 02fb99d into main Oct 6, 2026
5 checks passed
@jsulmont
jsulmont deleted the security/hardening-2026-10 branch October 6, 2026 05:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant