Repository navigation
fix(credentials): store the sign-in token through TinySecurity's keyring, roll back failed sign-ins, and guard artifact reads - #8536
Merged
M3gA-Mind merged 15 commits intoNov 5, 2026
Conversation
Contributor
M3gA-Mind
force-pushed
the
chore/followup-repin-1103
branch
2 times, most recently
from
November 4, 2026 01:57
4c806ce to
84de00d
Compare
M3gA-Mind
force-pushed
the
chore/followup-repin-1103
branch
from
November 4, 2026 13:39
84de00d to
f1b528c
Compare
This was referenced Nov 4, 2026
Merged
tinystoragedrivers 2d23a6a -> 5e8c837 (tinyhumansai#91). The new pin descends from the old one and is on tinystoragedrivers main's first-parent history.
… tiny-artifacts Move vendor/tiny-artifacts b8920f7 -> 6ee41ad (tinyhumansai#15), which adds FileRoots::with_async_guard and FileRoots::read. guarded_roots now runs TinySecurity's protected-path check as that guard, so a protected file is refused before its bytes are read. The legacy import and the visible-file copy read through FileRoots::read, which keeps the authorized file open, instead of resolving a path and opening it again.
Root and desktop lockfiles follow the new gitlinks: tinyconnectors 0.22.1, tinydocs 0.2.7 (ppt-rs without its PDF stack, calamine 0.38), tinyhosts 0.4.5, tinymcp 0.20.5, tinystoragedrivers-fence's libc dependency and tiny-artifacts' cap-fs-ext. No cargo update.
…hrough the guard These tests exercise artifact storage, not TinySecurity. Through the guarded roots every read now asks TinySecurity's protected floor. In the shared lib test process the module is admitted once with the first test's secret scope, so the query fails and the guard refuses every path. Scripting an empty floor keeps them on what they test; the new guard test scripts the one protected path it asserts.
…admission The shipped app loads TinySecurity as a pinned release, never from a local file. The native fixture now stages the verified archive as a bundled module with no local-file source and checks that later loads keep serving the admitted callback binding: a second client, the protected-path floor and redaction.
…l is stored A sign-in activated the new user's directory, purged the pre-login conversations and only then stored the credential. When the store failed, as every sign-in after a signed-out launch did, the purge had already run and users/<id> was left half-created with the profile switched. Activation now records what it changed. The pre-login purge waits until the credential is stored and the process has rebound, and any earlier failure restores the previous active user (or none) and removes a user directory the sign-in created.
…the native fixture TinyMCP, TinyEventQueues, TinyChannels and TinyComputer take host callbacks and ship as pinned releases. The native fixture now stages each verified archive as a bundled module with no local-file source, boots a desktop core so the storage host and secret store exist, and checks that each module admits and reloads. On Linux a module whose system libraries the runner lacks is skipped with a log line, and the rest are still checked.
TinySecurity is admitted once per process for one profile's secret key, and its secret, approval and audit identity cannot change after that. On a host that activates per-user profiles (the CLI, a TUI or a headless core), a sign-in into another profile moved the process to a profile its TinySecurity could not serve, and the credential write failed closed. Such a sign-in now activates the profile, stores nothing under the old one and answers restartRequired, so the user signs in again after a restart. The auth state reports restartRequired whenever the active profile is not the one TinySecurity serves (its live scope owner, or after a release the admitting scope). The CLI exits 75 with "restart required to finish switching profiles"; a server or TUI keeps refusing the other scope until it is restarted. The desktop keeps its profile in place, so it signs in without a restart.
native_fixture_serves_later_calls_after_a_release_admission (tests/security_release_reuse_fixture.rs) and native_fixture_loads_callback_modules_from_a_release_source (tests/callback_module_release_fixture.rs) load pinned module releases, so like the other native fixtures they are #[ignore] by default and run by scripts/ci/security-native-fixture.sh with --ignored --exact.
…nects A TinySecurity keyring operation waits for the user's approval, which the renderer receives only as a Socket.IO event. The socket connects once a session exists, so the keyring write that stores a first sign-in's token was never shown: the host waited out its 120 s and the sign-in failed. The host now keeps each pending prompt's display-safe metadata and serves it as openhuman.keyring_consent_list_pending_operations. While no socket is connected, the approval overlay polls it once a second, de-duplicated by request id against socket deliveries; a connected socket stays the only source. The same holds while a restarted desktop core is still in its secret preflight: reading a keyring-held session token at startup asks the user, and no socket exists yet. The preflight now also serves the keyring prompt list and its answer, the same class as approval_list_pending and approval_decide: they resolve an already pending request by its id and create no grant.
Re-pin vendor/tinysecurity b53eceb (v0.18.4) -> 17c1284 (v0.19.0), which carries the module-side keyring consent fix (tinysecurity#51) and contract 2.0 (tinysecurity#43). The registry record and its 11 digests come from the release's checksum.toml, each checked against the downloaded archive. Contract 2.0 adds an `abandoned` execution outcome. Nothing the host calls aborted proves the tool never ran, so `Aborted` now maps to the module's `Abandoned` instead of `Cancelled`, and a tool execution whose audit guard is dropped before it reports a result records `Aborted` instead of `Failure`. Rollback: a host or module on contract 1.x cannot decode a row that records `abandoned`.
An ignored fixture boots the desktop core the way the shell does (desktop_builder: secret preflight, embedder host) with TinySecurity from its bundled release and no Socket.IO, as on a first sign-in. It signs in with a session credential and answers each keyring prompt it can only see through keyring_consent_list_pending_operations: - approved, the token is stored and read back in the same process and the device is signed in, with no restart; - denied, sign-in fails and the device stays signed out with no token. A third case restarts the desktop core over the approved sign-in's workspace: reading the stored token during the secret preflight asks the user, and once that prompt is approved the core comes up signed in. The fixture script runs each case in its own process, on Linux under a private D-Bus session with an unlocked throwaway Secret Service, and lists the openhuman entries the keyring holds afterwards.
…t at 30 s The host's KeyringAccess2 call used tinybus' default 30 s deadline while the keyring prompt it waits on stays open for 120 s. A user who took longer than 30 s to approve still failed sign-in: the call timed out, the save fell back to the encrypted document, whose forced approval is refused at sign-in. Order the three deadlines so an unanswered prompt ends the same way every time: the host's own wait (120 s) ends first and records the prompt as pending, TinySecurity's consent deadline (125 s) after it, and the host's KeyringAccess2 call deadline (140 s, room for the keyring store) last. Compile-time asserts keep that order. A decided outcome the caller never took stayed in the registry and counted against its 64 slots until restart, as did a prompt whose waiter went away. An outcome now expires 30 s after its decision, and a waiter that goes away releases its reservation.
The desktop core serves only its approval methods until the secret preflight finishes and its services start. Nothing logged the steps in between, so a preflight that never finished looked like a silent gate. Log each step at debug level, and the hand-off from the preflight to the full router.
…builds With TinySecurity v0.19.0 every keyring operation waits for the user to approve it. Tests that save credentials have no user, so each save waited out the 120 s consent window and then failed: 156 tests that pass on main failed or timed out, and the measured suites took 25 times as long. A responder answers the credential store's prompts through the ordinary approval path. It is compiled only into unit-test builds, e2e-test-support builds (the WDIO and Playwright apps) and test-keyring-consent builds (the in-process integration tests, through the CLI's core dev-dependency), never a product build. OPENHUMAN_TEST_KEYRING_CONSENT picks approve, deny or off; unit tests approve by default, the other builds only when the lane scripts set it. Prompts from other callers stay unanswered, so tests of unanswered prompts keep working. check-feature-forwarding now fails if a test-only gate is in the product feature set or is reached from any crate's default features.
M3gA-Mind
force-pushed
the
chore/followup-repin-1103
branch
from
November 5, 2026 02:51
f1b528c to
9c6645a
Compare
M3gA-Mind
marked this pull request as ready for review
November 5, 2026 02:51
Tiny Sweeper review
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This was referenced Nov 5, 2026
This was referenced Nov 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #8530, rebased onto
mainafter #8534, #8535, #8540 and #8542, which already landed the module re-pins, the retained-binding reuse for TinySecurity and the callback-bound modules, and the scope takeover for embedded runtimes. What remains here:restartRequiredfor a sign-in into a profile its admitted TinySecurity cannot serve, instead of failing the credential write.vendor/tiny-artifacts), plus the last gitlink left on a formerly fork-only merge (vendor/tinystoragedrivers).abandonedexecution outcome.What was broken (on
main)users/<id>/created and the config moved, and it had already deleted the pre-login conversations.What changes
openhuman.keyring_consent_list_pending_operations. While no socket is connected, the approval overlay polls it once a second, de-duplicated by request id against socket deliveries. The core also serves this list andopenhuman.keyring_consent_respond_operationduring its startup secret preflight, so a restarted desktop that reads a keyring-held token at startup can ask the user. They are the same class asapproval_list_pendingandapproval_decide, already served there: local operator, the per-launch bearer, and they only resolve a request that is already pending, by its id; they create no grant.KeyringConfig2.consent_timeout_ms, which it accepts up to 300 s), so no TinySecurity change is needed. A decided outcome nobody takes now expires after 30 s, and a waiter that goes away releases its slot, so abandoned prompts can no longer fill the 64-slot registry until restart.restartRequired: true. The auth state reports it whenever the active profile is not the one TinySecurity serves (its live scope owner, or once released, the admitting scope; Let a later embedded runtime take over TinySecurity's secret scope once the first is released #8540). The CLI exits 75 with "restart required to finish switching profiles"; a server or TUI keeps refusing the other scope until restarted.abandoned, since its effects are unknown, instead offailed; the host's aborted outcome maps to the module'sabandoned.artifacts::tiny_store::guarded_rootsinstalls TinySecurity's protected-path check as tiny-artifacts' async guard, so a protected file is refused before its bytes are read.What users see
authcommand that moves to another profile prints "restart required to finish switching profiles" and exits 75; signing in again after the restart completes it.restartRequiredand keep refusing the other profile's security calls until restarted.Moved gitlinks
2d23a6a→5e8c837(#91 merge)b8920f7→6ee41ad(#15 merge)b53eceb(v0.18.4) →17c1284(v0.19.0)checksum.toml, each checked against the downloaded archivegit merge-base --is-ancestor old new) and is on the canonical repository'smain.cap-fs-extfor tiny-artifacts,libcfor tinystoragedrivers-fence, thetinysecurity-busversion line) in bothCargo.lockandcrates/openhuman-app/Cargo.lock, checked with--locked. Nocargo update.Rollback
Contract 2.0 can write
abandonedinto approval and audit rows. A host or module on contract 1.x cannot decode those rows, so rolling back past this PR on a device that has recorded one needs those rows migrated or cleared first.Validation (cuttlefish)
rust:layout, process-statics, module-boundaries, feature-forwardingcheck-module-pins(14 records),check-submodule-monotonicvsmain(2 forward)cargo check --locked: core, CLI, app,openhuman-modules-linked --all-targets --features alltype_complexityatobservability_reporting.rs:535, untouched here and red onmainKnown red, pre-existing on
main(not changed here)From runs against
9d3a7ae; the final CI run re-checks each against806fc50.contract_tests::a_product_feature_build_carries_all_thirteen_recordsandcontract_tests::every_record_matches_its_contract_constants(thesecurity-modulebuild carries 14 records).documents::tests::held_outputs_are_released_after_success_read_failure_and_cancellation,hosting::test::providers_and_launch_use_the_declared_bus_members_and_confidential_execute,hosting::test::workspace_resolution_refuses_absolute_traversal_and_symlink_escape.tests/security_native_fixture.rshas an unused import (openhuman_core::storage::DocumentStore), so clippy on that test target fails with-D warnings. This PR doesn't touch the file.agent::harness::tool_result_artifacts::tests::a_wrapped_read_is_pointed_at_the_materialised_filedepends on test order: the shared lib test process admits TinySecurity under the first test's secret scope.artifacts::-area tests fail even one-per-process (nextest), here and onmain:artifacts::files::tests::the_escape_guard_keeps_the_credential_store_floor,…::the_escape_guard_rejects_a_file_outside_its_folderand…::a_legacy_path_with_parent_components_is_rejected. All three hit "file is in a protected location" before the check they assert.agent::subagent_host::ops::runner_artifacts::tests::byte_policy_projects_a_parent_readable_artifact_before_the_result_cap. It gets[REDACTED]instead of the full result.mainfails 35.Lanes (rust-lint). Onmain, run the same way, these three steps fail identically:clippy-product:type_complexityatobservability_reporting.rs:535.tinyhumans-clippy: unusedpub use backend_error_code::HOSTED_VOICE_UNAVAILABLE_USER_MESSAGEatopenhuman-tinyhumans/src/inference/mod.rs:37.embed-offline-examples: thelinux_fleetexample builds withoutsecurity-module, so its prompt is held ("Prompt flagged for security review"). Behind it, theprofilesexample also fails, with "TinySecurity secret scope is unavailable".Lanes (checks),frontend:scripts-self-tests:stage-modules.test.mjsWindows VC-runtime cases fail withspawnSync unzip ENOENT.stage-modules.mjsextracts zips withunzip, and the CI image (.github/Dockerfile) doesn't install it.unzipto that image.Follow-ups
users/<id>, so every desktop account sharesusers/local. Restoring it brings back TinySecurity's one-identity-per-process limit on the desktop, which needs deferred admission with a settlement rebind (config snapshot, approval gate, live policy, text seams, MCP jail).ApprovalSecurityMiddlewarehas norecord_terminaloverride in production, so the audit rows of failed, cancelled and abandoned tool calls stayReserved. A production override was added in 2f60fd8 and removed in 26d12f6 (2026-10-25); restoring it is a behaviour change outside this PR.search::providers::tests::role_keys_match_the_contract_serde_namesfails onmainsince chore(modules): reconcile six module pins with their releases #8534: tinysearch's contract has four roles (Role::Images, tinysearch 6ec9048) and the host'sSEARCH_ROLESlists three. Not changed here.security/credentials/ops/credential.rsand its sibling modules.core/all.rsregisterscredentials::, not this copy.security/approval/native/engine_tests.rs(andengine_tool_trust_tests.rs,engine_origin_tests.rs) is declared by nomodand gated on an undeclared feature, so it never compiles; nothing checks the outcome the module stores. This PR'sengine_outcome_testscovers the host's outcome mapping only.Submission Checklist
operation_approval_testscover a pending prompt listed until decided, cancelled or expired; the controller contract tests cover the new RPC;keyringApicovers the new call;user_scope_testscover a rolled-back first sign-in (pre-login threads kept, no profile left) and a rolled-back account switch (previous user restored);session_state_testsandcli_testscover the restart status;policy_testschecks the contract 2.0 version;engine_outcome_teststhe abandoned outcome;tests/desktop_sign_in_fixture.rssigns in on the desktop core (secret preflight, no socket, TinySecurity from its release, a throwaway Secret Service): approved stores and reads back the token with no restart; denied stays signed out.Related
Open at merge time
mainpasses all four. Each raises one keyring consent; answering it removes the wait but not the failure. It's being diagnosed as a first-credential-flow-per-process effect introduced with v0.19.0; the fix, if one is needed, follows in a separate PR.