mirror of
https://github.com/logos-co/logos-rust-sdk.git
synced 2026-08-27 18:01:07 +00:00
* fix(origin): a module announces its OWN name, not "core"
plugin.rs hardcoded `let origin = CString::new("core")` when building a
module's outbound client, so every Rust module announced itself as "core" —
a TokenManager::bootstrapKeys() ANCHOR name — unprompted, in every handshake.
That is not a misattribution bug. "core" is an anchor name, so on a pre-0.8
module the provider's saveToken("core", T) lands in the OUTBOUND namespace,
which is exactly where the credential check reads. An ordinary
capability-minted pair token therefore BECOMES the provider's own credential.
Driven end to end: the caller's currentCaller() at the victim reports
caller_kind=host — a module authorizing AS THE HOST — and the victim's own
anchor is destroyed, locking the host out of that module. It also silently
widened any access policy whose allowedCallers contained "core".
Why the name had to be baked at generation time: the SDK genuinely cannot
learn it at runtime. The module-impl C ABI declares no self-name export, and
set_context's instance_id is a per-INSTANCE id derived from the persistence
path — often absent entirely. It IS known to lidl-gen as module.name, the same
string the derived name() method already uses and the same fact the C++
umbrella already bakes. So lidl-gen emits LOGOS_MODULE_NAME and the generated
ensure_ready() latches it into a set-once OnceLock ahead of the install hook.
Unset yields an EMPTY string, never a guess. Empty is fail-closed by name at
two independent gates — capability_module::requestModule rejects an empty
module name, and ModuleProxy rejects an empty caller — so a module that
somehow reaches the wire unlatched is refused rather than silently wearing
somebody else's identity.
The client cache is keyed (origin, target) rather than target: a client
carries its origin for life, so one constructed before the latch can never be
handed back after it. The construction still happens OUTSIDE the map lock —
outbound_origin() reads the OnceLock before the lock is taken — because on a
Qt-affine transport lp_client_create ends in runOnQtMainThread and holding the
lock there deadlocks.
ipc-test now asserts the announced origin across process boundaries: what the
caller announced, what capability_module admitted, the key the provider was
told to file under, the negative that no module may announce core or
capability_module, and an anti-vacuity check that a handshake actually
happened.
Also defines logos_module_accept_inbound_token (protocol 0.8). The binding is
declared inside the guarded emitted block rather than in api.rs: sharing an
rlib object with save_token pulled an undefined lp_token_save_inbound into
EVERY Rust module at EVERY protocol version, which ipc-test caught as a
dlopen failure.
Requires logos-protocol fix/token-direction-key-namespace (59b27ef).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): relock logos-protocol to 0.8 — the ABI check was passing vacuously
logos-protocol 480f40ff (0.5.0) -> 42460e5b (0.8.0, master)
WHAT MOVED, AND WHY IT HAD TO.
The direct `logos-protocol` input is read at exactly one place in this flake:
checks.module-impl-abi's ABI_MANIFEST (flake.nix:245). Everything else —
protocolVersion, lib.callerBuildSupport, and both fixture modules — reaches
protocol through logos-module-builder.inputs.logos-protocol instead.
So that one input decides the entire content of the ABI check, and at 480f40ff
it decided it to nothing. 480f40ff is protocol 0.5.0, whose declared module-impl
export list is TEN names; logos_module_accept_inbound_token is not among them,
and the accept_inbound_token_block this branch adds is gated on >= 0.8, so it
emitted nothing. The check compared ten against ten and passed — as did CI on
3179ff36, all four jobs green. The definition this branch exists to add was
never looked at by anything.
That is now fixed by the lock alone: no source change was needed or made.
module-impl C ABI check passed at protocol 0.8.0
Declared by logos-protocol (12 exports):
... logos_module_accept_inbound_token ...
... logos_module_set_call_caller ...
Defined by the Rust provider scaffold in all 4 configurations
Both 0.6's set_call_caller and 0.8's accept_inbound_token were invisible to the
10-name list; the relock brings both under the check in one step.
NON-VACUITY, MEASURED THREE WAYS.
Probe: move accept_inbound_token_block's gate from (0,8) to an unreachable
(99,0) so the export is not emitted, and build the check.
* at 42460e5b (0.8.0), lidl-gen doCheck off to isolate the check itself:
FAIL: [logos-rust-sdk provider scaffold (no-trait,multi), protocol 0.8.0]
DECLARED by logos-protocol but NOT DEFINED by this backend:
- logos_module_accept_inbound_token
module-impl ABI check FAILED: incomplete module-impl C ABI in
4 of 4 configuration(s)
RED, naming the symbol, in every emitter configuration.
* the SAME missing export at 480f40ff (0.5.0):
module-impl C ABI check passed at protocol 0.5.0
Declared by logos-protocol (10 exports): ...
GREEN. That is the vacuity, reproduced on demand: the old lock cannot fail
this check no matter what the emitter does with the 0.8 block.
* with lidl-gen's own doCheck left on, the crate unit tests also go red:
protocol_0_8_emits_the_inbound_token_export
the_0_8_gate_is_major_aware_not_minor_alone
the_outbound_door_stays_outbound_at_0_8
test result: FAILED. 50 passed; 3 failed
These pass explicit version strings and are lock-independent — they were
live before this relock; the nix check was not.
Gate restored to (0,8); the probes were separate clones and none of them is in
this commit.
CHECKS BUILT INDIVIDUALLY, x86_64-linux, upstream cache only
(cache.nix.logos.co is 502ing, so these are source builds):
.#checks.x86_64-linux.module-impl-abi
/nix/store/y2y3lj4qfi72s4w0pki5w5x65j7gpq3d-rust-sdk-module-impl-abi
.#checks.x86_64-linux.sdk-unit-tests
/nix/store/5f654q9i9hi8dl0wm1pxiw92risc2sm4-logos-rust-sdk-unit-tests-0.3.0
.#checks.x86_64-linux.ipc-test
/nix/store/0f28x0hv3r26am7g5raxxgjlyqfdka95-rust-sdk-ipc-test
path:./tests#checks.x86_64-linux.ipc-test (the CI job, with
--override-input logos-rust-sdk path:.)
/nix/store/ccrbbv3gsv767a2lghch2b5a84q4xyjy-rust-sdk-ipc-test
Both ipc-test derivations end with the assertion this branch is really about:
Origin passed: the caller announced "sdk_test_caller_module",
not a bootstrap anchor
WHAT THIS RELOCK DOES NOT REACH, SAID PLAINLY.
ipc-test's fixtures build through logos-module-builder, whose own
logos-protocol is still 480f40ff (0.5.0), and their provider_gen.rs scaffolds
are checked in, generated at 0.5. So the origin assertions above are proven at
protocol 0.5.0, and logos_module_accept_inbound_token is NOT exercised at
runtime by any check here — module-impl-abi covers it at codegen level only.
Confirmed against the built plugin rather than assumed:
nm -D --defined-only sdk_test_provider_module_plugin.so | grep logos_module_
-> the ten 0.5 exports, no accept_inbound_token, no set_call_caller
nm -D --undefined-only ... | grep '^lp_' -> nothing at all
which is also the positive check on the known trap: the lp_token_save_inbound
binding stayed inside the guarded emitted block, so it did not leak an
undefined symbol into a module built at 0.5. Had it been moved to api.rs, that
grep would have printed it and the plugin would have died at dlopen().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>