386 Commits
Author SHA1 Message Date
Alexey Cluster fbf8ff2d63 signing_test: drop the stale Catch2 header note
ssdp_listener_test.cpp is main()+CHECK too, and Catch2 is not in the
tree. The note still claimed it was the one Catch2 suite and that
converting this file was deferred.
2026-10-14 07:20:54 +02:00
Santiago Postorivo f356b164a6 tests: SSDP golden fixtures, push_status delta-merge spec, signing trailing-sibling lock (#114) 2026-10-14 07:16:56 +02:00
Alexey Cluster d6e0cb95ff Logging certificate id for easier remote debugging
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-14 07:01:07 +02:00
Alexey Cluster d8d8561cb5 Add auth_probe action to plugin_runner
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-10 01:42:51 +02:00
Alexey Cluster b974e97e96 local_print: honor cancel during the post-upload MQTT reconnect
ensure_lan_session() can block for a few seconds waiting for CONNACK.
A cancel that arrives in that window must not publish project_file, and
must be reported as a cancel rather than a failed reconnect.
2026-10-10 01:03:31 +02:00
Sashaandsasha_nordiclab 293ce9446f Fix LAN print reconnect hangs (#120)
Co-authored-by: sasha_nordiclab <sasha@nordiclab.org>
2026-10-10 01:02:37 +02:00
persano 201bde869f docs(research): reword survey/crib references to repo-neutral provenance
Workspace-only artifacts (INTERESTING_REPOS.md, A1_DIAGNOSTIC_CRIB.md, REMOTE_CAMERA_INVESTIGATION_HANDOFF.md) do not exist in this repo; tags become 'community-survey cross-check #N' and dead links are dropped while keeping every sourced fact.
2026-10-09 01:08:05 +02:00
persano e67a10cc00 docs(03-03): tracer - 06.02 numeric sequence_id experiment lead (SIGN-02)
- Experiment (open lead) paragraph inserted after the cross-check #3 tag, before the Firmware quirk paragraph
- Hypothesis A/B pre-registered outcomes as error families only (0x0502400A / mqtt message verify failed)
- cross-links to A1_DIAGNOSTIC_CRIB.md and OrcaSlicer #15812; doc-only, default behavior unchanged
2026-10-09 01:08:05 +02:00
persano c4f7f4781e docs(research): 10.04 consensus note-to-row provenance map (DOC-03 audit)
- maps each of the six mandated notes to its INTERESTING_REPOS.md source row
- citation audit: AGPL facts-only (fence count 6, SIGNED_VS_UNSIGNED absent), both-readings register intact, insertion-only
2026-10-09 01:08:05 +02:00
persano 95ee637017 docs(03-01): cert_id farm colon-variant footnote + hard-rules pointer to consensus
- blockquote footnote under Key table, Source: INTERESTING_REPOS.md cross-check #7
- one-line pointer at byte-identical hard-rules paragraph -> Signing consensus
- insertion-only (5 insertions, 0 deletions)
2026-10-09 01:08:05 +02:00
persano df72f44a62 docs(03-01): tracer - Signing consensus subsection with six mandated notes
- family key first / insertion order / Trap sort_keys / trailing sibling / cert_id / sequence_id
- both-readings register, Source: INTERESTING_REPOS.md cross-check #2 tag
- insertion-only, AGPL facts-only (IMPROVEMENTS.md link, no code)
2026-10-09 01:08:05 +02:00
persano 270d67bd52 docs(01-03): tighten 06.02 A1 cross-reference to direct A1_DIAGNOSTIC_CRIB.md link (SUP-01) 2026-10-09 01:08:05 +02:00
persano e057ce9f6d docs(01-03): add two-way back-links to 16-quirks.md on the six remaining affected pages (D-12/D-14) 2026-10-09 01:08:05 +02:00
persano a5eee6a0b0 docs(01-03): add community quirk index page + INDEX registration + first back-link (CAT-01) 2026-10-09 01:08:05 +02:00
persano 349b12969b docs(01-02): reword 10.04 block heading so global pointer census counts exactly 3 2026-10-09 01:08:05 +02:00
persano 7332c72ee0 docs(01-02): document sequence_id firmware-dependent string/number split in 06.02 + third back-pointer (DOC-06) 2026-10-09 01:08:05 +02:00
persano a787221624 docs(01-02): extend 08.08 md5 row with 3-way split + add two 84033544 back-pointers (DOC-04) 2026-10-09 01:08:05 +02:00
persano dc6ec9ed73 docs(01-02): add 84033544 two-readings clarification block + LAN-X509 family to 10.04 (DOC-04) 2026-10-09 01:08:05 +02:00
persano bf241fde74 docs(01-01): record full LVL_Local reject bytes in 06.04 (DOC-05) 2026-10-09 01:08:05 +02:00
persano fa94a2a256 docs(01-01): add BRTC ambiguity callout + pointer edits (DOC-02) 2026-10-09 01:08:05 +02:00
Alexey Cluster 9ec9c3d847 filter_mqtt_hms_65543 option to filter the "MQTT verification failure" error
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-09 00:52:42 +02:00
Alexey Cluster 7f11cef262 STATUS.md update about bambu_network_get_camera_url and TUTK
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-09 00:02:02 +02:00
persano 73d923b439 Revert "camera: drop client-side liveview.prepare and liveview signing"
This reverts commit 8080cb9ab7.
2026-10-08 23:39:11 +02:00
Alexey Cluster 7243b88122 Research
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-08 23:33:15 +02:00
Alexey Cluster 2cfd2ba412 Fixed app_cert_list
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-05 00:16:23 +02:00
Alexey Cluster 93d7d468d5 Research about TUTK
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-10-05 00:10:33 +02:00
Alexey Cluster 8656b66125 More info about multiple extruders
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-30 22:21:04 +02:00
Alexey Cluster 8c5258f453 Faster liveview start
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-30 20:31:27 +02:00
Alexey Cluster fcba35db10 Research about TUTK and some multi-nozzle stuff
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-30 20:20:44 +02:00
Alexey Cluster 67e994bee3 Advanced debug logging for BambuSource
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-29 17:51:07 +02:00
Alexey Cluster 970633f92e Add handshake logging and byte description for improved debugging
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-29 10:53:26 +02:00
Alexey Cluster 8fe6c6ad8e Add OpenSSL host check functionality and related tests
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-29 09:35:07 +02:00
Alexey Cluster 77849e4d5b File transfer over TUTK
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-28 00:27:32 +02:00
Alexey Cluster 36fa964b9f Fixed storage browser
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
2026-09-27 23:35:53 +02:00
Alexey Cluster 0deaa46ded ftps: bound control-channel input, parse PASV without sscanf (#102)
- read_line() grew ctrl_buf until a newline showed up, and read_reply()
  accumulated "NNN-" continuation lines forever. The per-read timeout
  does not help while data keeps arriving. Cap a line at 16 KiB and a
  reply at 256 KiB and fail the command beyond that.
- The 227 reply was parsed with sscanf("%d"), which is undefined on
  out-of-range input, and nothing checked the fields were 0..255.
  detail::parse_pasv_reply() now requires six plain decimals in range
  and a non-zero port; ftps_parse_test covers it.

Checked against a fake server streaming a newline-less banner and an
endless multi-line 220: both fail after the cap instead of buffering
megabytes.
2026-09-27 23:00:39 +02:00
Alexey Cluster f0cd7750ff auth: create the token file 0600 before writing to it (#102)
persist_locked() opened obn.auth.json.tmp with std::ofstream - created
with the umask's mode, typically 0644 - wrote the access and refresh
tokens into it, and only then chmod'ed it to 0600. Another local user
could read the tokens in that window, and a stale .tmp from an older
build kept whatever mode it had.

Pre-create the temp file with open(O_CREAT, 0600) plus fchmod() for the
stale case; rename() carries that mode over, so the chmod after the
fact is gone. umask is process-wide, so not an option inside the
slicer. The chmod on load still tightens files written by old builds.

auth_persist_test checks the result under umask 022 with a
world-readable stale .tmp in place; it fails without the fix.
2026-09-27 23:00:37 +02:00
Alexey Cluster 0abe735875 http: redact credentials in the OBN_HTTP_TRACE header log (#102)
The opt-in curl trace logged request headers verbatim, including
Authorization: Bearer <access token> - and people attach these logs to
GitHub issues. Mask the values of Authorization, Proxy-Authorization,
Cookie, Set-Cookie, x-amz-/x-oss-security-token and
x-bbl-device-security-sign, keeping the auth scheme word and the value
length so the log stays useful for debugging signatures.

http_redact_test covers the header block shape libcurl passes, case
insensitivity, look-alike names left alone, and degenerate lines.
2026-09-27 23:00:32 +02:00
Alexey Cluster f4ec706a99 agent: fix use-after-free races on the LAN/cloud/SSDP sessions (#102)
Agent copied lan_session_, cloud_session_ and discovery_ out as raw
pointers under mu_, unlocked, then used them - while connect_printer(),
disconnect_printer(), disconnect_cloud() or start_discovery(false) could
move the same member out and destroy it. send_message_to_printer() even
does RSA signing in that window. connect_cloud() used cloud_session_
without the lock at all.

One level down the sessions had the same bug: LanSession/CloudSession
disconnect()/stop() reset client_ while publish_json()/publish() and
is_connected() on another thread were still using it. That is the one
ASan catches first in a stress run (publish on one thread, connect/
disconnect on another): heap-use-after-free in mqtt::Client::publish,
freed by LanSession::disconnect.

- The three Agent members and the sessions' client_ are shared_ptr;
  callers copy them under the lock and use the copy.
- Whoever takes one out of its member now calls disconnect()/stop()
  explicitly (connect_printer's previous session and a racing
  replacement included). That joins the network thread, so the last
  release - which may now happen on another thread - is a plain destroy
  and never lands on the object's own thread.
- mqtt::Client callbacks use a raw pointer to the client that owns them
  instead of re-reading client_.

The same stress run is clean after the change, with mqtt_keep_connection
on and off (~1.2M publishes / ~3k reconnect cycles per 8 s run).
2026-09-27 23:00:23 +02:00
Alexey Cluster e7871f3f51 tests: cover the untrusted-input bounds from #103
- json_lite_test (new): nesting at and past the cap for arrays, objects
  and mixed containers; a million-deep payload is rejected, not a stack
  overflow; sibling branches give their depth back; as_int() clamps
  +-1e300 and +-2^63 and leaves ordinary values alone.
- tunnel_local_test: upload init reply accepts chunk_size 1..65536 KB and
  rejects 0, negative, 65537, 2^32 and 1e300; rejects a negative resume
  offset.

Both fail if the cap is reverted to 512 or the offset check is removed.
2026-09-27 22:41:17 +02:00
Alexey Cluster 720685208a tunnel_local: reject a negative resume offset in the upload init reply
offset is printer-supplied like chunk_size. A negative value wrapped to
~2^64, so the resume-prefix hashing loop read the whole file before
failing with a misleading "read error". Reject it at parse time.
2026-09-27 22:41:14 +02:00
Alexey Cluster aed0ecac3b json_lite: lower the nesting cap from 512 to 128
Each level costs about 0.5 KB of stack with GCC, so 512 levels needed
~210-250 KB. Parsing runs on threads we don't size - mosquitto's loop
thread (push_status) and detached std::threads - which get 512 KB by
default on macOS, leaving a thin margin once the callers' frames are
counted. Real Bambu payloads nest in single digits, so 128 still leaves
an order of magnitude of headroom.
2026-09-27 22:41:12 +02:00
Alexey Cluster ad34849d1c ft: drop the session and avoid a double result when a job throws
The catch-all added in #103 reported FT_EIO unconditionally. If the throw
came after the job had already delivered its result, Studio's callback ran
twice. And a throw mid-transfer can leave the shared TLS session part-way
through a frame, which the next job would reuse because is_connected()
still says yes. Disconnect it and only report if nothing was reported.
2026-09-27 22:41:10 +02:00
Alexey Cluster 8ecccd502c ft: call start_connect callbacks outside lan_mu
ft_tunnel_start_connect() invoked Studio's connect and status callbacks
while holding lan_mu - the same pattern #103 fixed for the upload and
download result paths. On a failed connect FileTransferObject fails its
pending requests, which can reach reset_locked() -> ft_tunnel_shutdown()
and re-lock lan_mu on the same thread.
2026-09-27 22:41:07 +02:00
Fernando Marino`andFernando Marino b0d87b502e fix: harden JSON parsing and the FT job thread against malicious wire data (#103)
Three related bugs, all reachable from untrusted printer/cloud input
since the plugin trusts wire data it hasn't fully validated:

1. json_lite.cpp: Parser::parse_value() recursed once per nested {}/[]
   with no depth limit. A deeply-nested JSON payload (e.g. thousands of
   nested '[') exhausts the stack and crashes the host process, since
   this plugin runs in-process via dlopen, not sandboxed. It's reachable
   from raw MQTT push_status payloads (agent.cpp's update_fw_state) and
   from any HTTP/cloud response body. Cap nesting at 512 levels.

2. json_lite.hpp: Value::as_int() did a bare
   static_cast<int64_t>(double), which is undefined behavior when the
   number is NaN or outside int64_t's range - both are things wire JSON
   can freely contain. Clamp instead.

3. tunnel_local.cpp: parse_upload_init_reply() read the printer's
   FILE_UPLOAD-init reply.chunk_size with no upper bound. That value
   directly drives `buffer_size = chunk_size_kb * 1024` and
   `std::vector<char> buffer(buffer_size)` in tunnel_upload.cpp. A
   crafted reply (e.g. chunk_size: -1) requests a multi-terabyte
   allocation, throwing std::bad_alloc/std::length_error. That runs on
   the detached job thread spawned by abi_ft.cpp::spawn_job() with no
   exception handling, so the exception escapes and calls
   std::terminate() - the whole host process dies from one small
   malicious reply. Reject anything above 64 MiB (observed real
   firmware reports low hundreds of KB).

Also, as defense in depth for #3 and any other not-yet-found exception
source on that same thread: wrap spawn_job()'s job dispatch in
try/catch and report FT_EIO instead of letting any exception reach
std::terminate().

Separately, abi_ft.cpp's connect-failure error path called
deliver_result() (and thus the host's result callback, synchronously)
while still holding FT_Tunnel::lan_mu. Studio's FileTransferObject
calls ft_tunnel_shutdown() from that callback for one-shot mem
downloads, which re-locks the same non-recursive mutex on the same
thread -> deadlock. Move the error delivery outside the locked scope,
matching the pattern the success path already used.

Verified: all four files pass `g++ -std=c++17 -fsyntax-only`. The
self-contained json_lite fix is additionally covered by a standalone
test exercising deep nesting (now rejected instead of crashing),
normal/moderately-nested JSON (still parses), and as_int() clamping on
NaN/out-of-range doubles - all pass. The abi_ft.cpp/tunnel_local.cpp
fixes could not be exercised end-to-end (no Bambu printer available,
and the full build needs MSVC v142 + vcpkg), so please review the
locking/allocation-bound logic carefully before merging.

Co-authored-by: Fernando Marino <f.marino@rheagroup.com>
2026-09-27 22:34:13 +02:00
Alexey Cluster 1c05b02b2b cloud mqtt: honor lan_tls_skip_verify
Cloud MQTT now verifies chain and hostname on every platform, which
breaks behind a TLS-inspecting proxy whose root is not in the vendored
bundle, or once that bundle goes stale. lan_tls_skip_verify (and
OBN_SKIP_TLS_VERIFY) now disables both checks for cloud MQTT too, so
there is an escape hatch without a rebuild. The effective setting is
logged on connect as tls_verify=0/1.

Docs: obn.conf template, README and STATUS describe the wider scope and
no longer claim slicer_base64.cer is used for Windows cloud MQTT.
2026-09-27 22:19:20 +02:00
Alexey Cluster 396b6a1d3a mqtt: let Windows TLS come up without a CA file
The Windows no-ca_file branch meant to fall back to OpenSSL's default
verify paths, but mosquitto_tls_set() rejects cafile=capath=NULL with
MOSQ_ERR_INVAL, so connect() always failed there. Turn on
MOSQ_OPT_TLS_USE_OS_CERTS instead (mosquitto enables TLS on that alone)
and only call mosquitto_tls_set() when there is a path to hand it.

With verification on this still fails closed on Windows, since the static
OpenSSL has no default store; with tls_insecure it now connects.
2026-09-27 22:19:20 +02:00
Alexey Cluster fac7dd4279 cmake: embed the cloud CA bundle on Windows only, split it on newlines
- Linux/macOS take the system trust store, so the ~189 KB bundle was dead
  weight in their binaries.
- Register resources/cacert.pem as a configure dependency; it is read by
  file(READ) at configure time, so an updated bundle was silently ignored
  until a manual reconfigure.
- End every raw-string chunk on a newline instead of a fixed byte offset.
  The bundle carries non-ASCII CA names, and with /utf-8 a chunk that
  splits a multi-byte sequence gets mangled. The current bundle happens to
  be safe; the next refresh might not be.
2026-09-27 22:19:20 +02:00
Alexey Cluster df2039b1e1 cloud_ca_bundle: fix stale references in comments
The header pointed at a nonexistent cmake/EmbedCloudCaBundle.cmake, and
connect_cloud() credited CloudSession::configure() with the verification
flags that actually live in CloudSession::start().
2026-09-27 22:19:20 +02:00
Fernando Marino`andFernando Marino b55fea84f1 Fix: verify the cloud MQTT TLS certificate on Windows instead of skipping it (#104)
* fix: verify the cloud MQTT TLS certificate on Windows instead of skipping it

connect_cloud() handed CloudSession the BBL slicer cert bundle Studio
passes via set_cert_file() as the cloud MQTT trust anchor, purely so
mosquitto_tls_set() wouldn't reject a null cafile (vcpkg's static
OpenSSL ships no default trust store on Windows). Since that bundle's
roots don't actually sign *.bambulab.com, CloudSession then set
tls_skip_chain_verify=true and called mosquitto_tls_insecure_set(true)
-- disabling *both* certificate-chain and hostname verification for
the cloud connection, unconditionally, whenever a cert bundle path was
configured (i.e. on every normal Windows run).

That's a real gap: with verification off, an active MITM (rogue AP,
ARP/DNS spoofing, a malicious proxy) can terminate the TLS session
itself and read the plaintext MQTT password, which is the user's live
cloud bearer token -- enough to fully impersonate them against the
real Bambu cloud API until the token expires. The comment justifying
the skip ("MITM gets opaque traffic but no usable credentials") does
not hold once verification is disabled.

Checked what's actually needed: *.bambulab.com serves an ordinary,
publicly-trusted certificate --

  $ openssl s_client -connect us.mqtt.bambulab.com:8883 \
        -CAfile cacert.pem
  ...
  Verify return code: 0 (ok)

against curl's official cacert.pem (a standard Mozilla-derived bundle,
the same kind curl's own Windows build already uses successfully for
this project's HTTPS calls to api.bambulab.com). It chains to DigiCert
Global Root G2 -- nothing Bambu-specific required.

So: vendor that bundle (resources/cacert.pem, compiled in via
cmake/cloud_ca_bundle_data.cpp.in so the plugin doesn't depend on a
companion file being installed alongside it), write it to
<config_dir>/obn_cloud_ca.pem on Windows, and hand mosquitto that
instead of Studio's BBL bundle. CloudSession::start() no longer skips
chain or hostname verification for the cloud path -- the CA bundle it
receives is now one that actually validates the real chain, so the
normal cfg.tls_insecure = false already at the top of the function is
correct and sufficient.

Linux/macOS are unaffected (unchanged: empty ca_file falls through to
mqtt_client's distro-trust-store search, which already works).

* fix: split the embedded CA bundle into chunks (MSVC C2026)

MSVC caps a single string literal at 65535 bytes; the ~189 KB vendored
bundle blew past that as one big raw string. Split it into ~8000-byte
chunks emitted as adjacent raw-string literals -- the compiler
concatenates those into one string constant at compile time with no
limit on the total length. g++'s earlier -fsyntax-only check didn't
catch this since GCC has no such per-literal limit; caught by an
actual MSVC build.

---------

Co-authored-by: Fernando Marino <f.marino@rheagroup.com>
2026-09-27 22:03:30 +02:00
Alexey Cluster 913ac68811 camera: don't use port 6000 when LAN liveview is off
prefer_rtsp treated an open CTRL port as a camera and handed Studio a
LAN URL with no RTSP hint, so BambuSource spoke MJPEG. A printer that
reports rtsp_url "disable" now falls through to TUTK instead.
2026-09-27 21:43:48 +02:00