mirror of
https://github.com/logos-co/logos-module-loader-qt.git
synced 2026-08-30 22:01:09 +00:00
* feat: deliver the host-services grant to privileged modules (C3)
Completes the host half of the escape hatch. capability_module can now be an
ordinary universal module that asks for its privileges, instead of a
hand-written Qt plugin reaching into TokenManager directly.
The grant rides the SAME route the auth token already takes — a launch argument
to the module's own logos_host process, stamped as a property on the LogosAPI
object, which the cdylib glue forwards across the module-impl C ABI. Not a new
loader/container RPC: I started down that path (a grantHostServices virtual on
LogosCore::ModuleLoader) and backed it out, because the grant must be in place
BEFORE the plugin's provider init() runs, and init() is exactly where a cdylib
forwards it into its own image. A post-load RPC would arrive too late and would
have needed a new virtual across three repos.
It has to reach the module's own image at all because the host binary and a
module cdylib each link their own copy of logos-protocol, so each has its own
process-global grant state — measured, not assumed: a built plugin DEFINES 25
lp_* symbols and imports zero. A grant the host recorded for itself would leave
the module's gates shut, silently, since lp_token_keys() just returns null.
The policy is host-side and bound to the module NAME the registry trusts, not
to anything the module asserts about itself; metadata.json#host_services is
advisory, this is the authority. It sits after loadModule()'s F-022 name check,
so the identity the grant is bound to has already been verified against the
binary. Hardcoded rather than configurable: these two services let their holder
enumerate the token store and hand authority to an arbitrary target, which is
capability_module's job and nothing else's.
dynamic_calls is deliberately NOT granted here — it is elevated but not a trust
root, so it belongs with the per-module access policy the daemon already
applies alongside allowedCallers, rather than in a table whose whole value is
staying at one entry.
An ordinary module gets no flag, so no property, so no call: absence is what
keeps it fail-closed. lp_grant_host_services REPLACES rather than adds, so
pushing an empty array would be a pointless clear.
Verified in the built artifacts: logos_host --help lists --host-services, and
liblogos_module_loader_qt.a carries the compiled policy string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): stop carrying JSON across the command line
Windows' CommandLineToArgvW treats `"` as a quoting delimiter and CONSUMES it,
so a JSON argv element arrives at the child stripped of its quotes and no longer
parses. POSIX exec() passes argv through untouched, which is why this was
invisible on Linux and macOS. Two arguments were affected.
--host-services. hostServicesFor() emitted `["token_registry","token_delivery"]`;
capability_module's process received `[token_registry,token_delivery]`, nlohmann
discarded it, and lp_grant_host_services rejects an unparseable list WHOLESALE
by design. Measured on a real Windows run: the host logged the correct JSON, the
module process logged the stripped form, and the impl answered
[capability_module] host services refused: "[token_registry,token_delivery]"
which was the only visible symptom — the trust root silently lost both services
while the run otherwise looked healthy (0 refused calls, 3 modules loaded).
Fixed by carrying a BARE COMMA-SEPARATED LIST over argv and re-serialising it to
a JSON array in module_initializer before the property is stamped. Service names
are a closed set of [a-z_]+ identifiers, so nothing needs quoting. The two places
that genuinely require JSON — the `hostServices` property the cdylib glue reads,
and lp_grant_host_services itself — are unchanged, so logos-plugin-qt and
logos-protocol need no edit.
--transport-set. Same defect, not yet observed only because moduleTransportsMap()
was empty for the modules under test; any module with an explicit transport entry
would hit it. The payload here is arbitrary nested JSON (endpoints, ports, TLS
paths), so it cannot be flattened into an identifier list — it is base64-encoded
instead. The alphabet is [A-Za-z0-9+/=], with no quote, space or backslash, so it
survives any command-line reconstruction.
The receiver accepts both forms and the discrimination is exact rather than
heuristic: a JSON transport set always begins with `{` or `[`, and neither
character is in the base64 alphabet. An older daemon paired with a newer
logos_host therefore keeps working. A payload that is neither is passed through
unchanged so the error surfaces in transportSetFromJsonString, where it is
diagnosable, rather than as a silently empty transport set.
base64Encode is hand-rolled because qt_plugin_format_loader.cpp is deliberately
Qt-free (boost + spdlog + std); the decode side is already a Qt TU and uses
QByteArray::fromBase64 with AbortOnBase64DecodingErrors.
Verified on framework.lan: x86_64-linux logos-basecamp and
logos-basecamp--host-services-test, and x86_64-windows
logos-basecamp--bin-bundle-dir, all EXIT=0. Then re-run on real Windows with the
same 100s script as the failing run: the module now logs the proper JSON array,
the "host services refused" line is gone, REFUSING = 0, "rejecting unauthorized
call" = 0, 3 modules loaded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): relock onto the merged B3 SDK masters
The lock was pinned at 2026-08-11 revs, which predate the entire B3/B4 SDK
split. Nothing in flake.nix was rev-pinned — every logos-co input already
carried a plain master url — so this is purely a stale lock, and it made the
repo's own CI misleading: `nix build .#checks.<sys>.tests` resolves from
flake.lock, so a green CI run on this branch was proving the code builds
against the PRE-split SDKs, not against what master now is.
Relocked with an explicit `nix flake lock --update-input <name>` per input,
because a bare `nix flake lock` does not re-resolve an input that is already
locked:
logos-protocol 03842db5 (08-11) -> f4407ff4 (08-19) #59 + #60
logos-cpp-sdk e3744fb8 (08-11) -> 95d7b3a9 (08-19) #138
logos-qt-sdk c6be61d0 (08-11) -> 19c844f2 (08-19) #33
logos-nix 6e0f4a71 (08-10) -> f55bf91b (08-14)
logos-module, logos-container and logos-module-loader were already at their
master heads and are untouched. logos-plugin-qt enters the lock for the first
time, at 9b2c64e5 (#19), pulled in transitively by the new logos-qt-sdk — that
is the repo that now owns the cdylib glue reading the `hostServices` property
this branch stamps, so the producer and consumer are locked in agreement.
Verified rather than assumed: the closure resolves to exactly ONE nixpkgs rev
(e9f00bd8) across all eight nixpkgs nodes, so there is no Qt version conflict.
Root `nixpkgs` resolves through its follows to nixpkgs_5, not to the node that
happens to be keyed `nixpkgs`.
logos-lidl stays at ffeebf2e even though lidl master is b6a9749a: it is not a
direct input here and has no follows, so it is whatever logos-cpp-sdk and
logos-qt-sdk themselves locked. Both agree on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: cover the argv encoding contract, and correct the --host-services help
The second commit on this branch fixes a Windows-only defect that shipped
because nothing tested what buildArguments actually puts on the command line.
The existing BuildArguments_* cases covered --name, --path and
--instance-persistence-path and stopped there, so both new arguments went in
unguarded and the quote-eating bug had to be found by hand on real Windows.
Five cases, at the only level where the anonymous-namespace helpers are
observable — buildArguments' output:
- capability_module receives token_registry,token_delivery
- an ordinary module receives NO --host-services flag at all (absence is
what keeps it fail-closed, so it is worth asserting explicitly)
- dynamic_calls is not in the grant
- --transport-set is base64, checked against known answers for all three
padding cases, since the encoder is hand-rolled and the tail bytes are
where such an encoder regresses
- --transport-set is omitted when empty
The load-bearing assertion in the two argv-encoding cases is not "the value is
correct" but "the value contains no `"`, backslash or space" — the characters
CommandLineToArgvW reconstructs destructively. A plain round-trip assertion
would have stayed green on Linux and macOS throughout the original outage,
which is exactly how the bug survived.
Checked that these bite rather than merely pass: reverting both halves of the
fix in qt_plugin_format_loader.cpp (grant back to a JSON array, transport set
back to raw JSON) fails precisely
BuildArguments_GrantsHostServicesToCapabilityModule and
BuildArguments_Base64EncodesTransportSet and nothing else. Restored, and the
tests derivation rebuilds to the identical store path.
Also corrects two pieces of documentation the Windows fix left describing the
old format. `logos_host --help` still advertised --host-services as "a JSON
array", and the ModuleArgs::hostServices comment said the same, while the flag
has taken a bare comma-separated list since b4529e0. Passing the documented
JSON form now yields a wholesale refusal — fail-closed, but for a reason the
help text actively misdirects you away from. Verified in the rebuilt binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>