Skip to content

fix(kernel): require a verified caller identity for the secrets and RBAC IPC bridges - #505

Merged
kshivang merged 4 commits into
risa-labs-inc:devfrom
Antriksh1984:fix/kernel-ipc-bridge-caller-identity
Sep 13, 2026
Merged

kshivang merged 4 commits into
risa-labs-inc:devfrom
Antriksh1984:fix/kernel-ipc-bridge-caller-identity

Conversation

@Antriksh1984

@Antriksh1984 Antriksh1984 commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

BossConsole#53 built a full caller-identity verification system for the kernel's out-of-process IPC transport - ProcessIdentityInterceptor (a ServerInterceptor) plus ProcessTokenRegistry (mints a per-process credential at spawn, never trusts a request body). It's wired into BossIpcServer in production and PluginUIServiceBridge uses it correctly, with real per-surface ownership checks in RemoteUiSurfaceRegistry.

But ProcessIdentityInterceptor's own KDoc says it plainly: "fourteen other service bridges as of writing, none of them authenticated today." The interceptor establishes identity on every call; it's each service's own choice whether to check it, and only one of fifteen bridges makes that choice.

This PR closes that gap for the two bridges where it matters most:

  • SecretServiceBridge - the entire out-of-process password vault surface: list, search, create, update, delete, share, unshare.
  • RoleManagementServiceBridge - RBAC: list/create/delete roles and permissions, grant/revoke a permission on a role.

Before this, any out-of-process plugin that could reach the kernel's local IPC socket had unrestricted, unattributed access to both. Not a network-remote threat (the socket is user-scoped), but a real gap against a rogue or compromised plugin process - exactly the class of concern #53 raised, on a more sensitive surface than the UI transport it originally fixed.

What changed

Both bridges now call an authenticatedCallerOrRefuse(rpc) helper (mirroring PluginUIServiceBridge's own) at the top of every RPC method - reads included, not only mutations, since an unattributed read of the secret list is exactly the exposure this closes. A call with no verified AUTHENTICATED_PROCESS_ID in its gRPC Context is refused with PERMISSION_DENIED before it ever reaches the underlying SecretDataProvider / RoleManagementProvider. Every mutating call additionally logs the verified caller identity (LogCategory.AUTH), so a compromised plugin's vault/RBAC activity is attributable after the fact rather than just blocked.

No behavior change for any real plugin. Every process spawned through ai.rever.boss.process.ProcessSpawner (the only spawn path in production) already receives a BOSS_PROCESS_TOKEN env var, and ChildProcessBootstrap already attaches ProcessTokenClientInterceptor to its kernel channel whenever that token is present. The only callers with no credential are processes started outside that path - tests, and exactly the kind of caller this closes the door on.

Test plan

New SecretServiceBridgeTest (9 tests) and RoleManagementServiceBridgeTest (10 tests), both exercised over a real gRPC server and real client - same harness PluginUIServiceBridgeTest already established (ProcessIdentityInterceptor + ProcessTokenRegistry on a real ServerBuilder, ProcessTokenClientInterceptor on a real ManagedChannel, an anonymousCaller() with no credential at all).

Every refusal test asserts two things: the RPC fails with PERMISSION_DENIED, and the underlying provider's call counter stayed at zero - the assertion that actually matters, since a refusal that still reached the vault would defeat the point.

  • ./gradlew :composeApp:compileKotlinDesktop :composeApp:compileTestKotlinDesktop - clean
  • ./gradlew :composeApp:desktopTest --tests "ai.rever.boss.kernel.services.*" - all green, including the pre-existing PluginUIServiceBridgeTest suite (no regression)
  • ./gradlew :boss-ipc:test - clean
  • ./gradlew ktlintCheck detekt - clean

Deliberately out of scope

The other twelve unauthenticated bridges (ActiveTabsServiceBridge, ContextMenuServiceBridge, DirectoryPickerServiceBridge, DownloadServiceBridge, GitServiceBridge, LogServiceBridge, NotificationServiceBridge, PanelEventServiceBridge, PerformanceServiceBridge, ProjectDataServiceBridge, RunConfigServiceBridge, SplitViewServiceBridge, SupabaseServiceBridge) are untouched. Their worst case is stale/crossed UI or workspace state rather than credential/permission exposure, and extending the pattern to all of them is a much larger, more mechanical follow-up better done as its own PR (or several) rather than bundled here.

Duplicate submission provenance

This PR is the surviving implementation for #598, also authored by Antriksh (@Antriksh1984). Both submissions gate the same nine Secret Service RPCs before provider access using the same verified identity and PERMISSION_DENIED result. #505 additionally retains attributed mutation logging, RBAC protection, individual refusal tests, token-revocation coverage and late-service registration coverage. #598 was reviewed against #505 at 7bf4f6e0db504495cfebf6e76ba552933d2cce0c; it supplies no additional production behavior. Its positive workflow test exercises existing provider mapping rather than a distinct fix. Contributor credit belongs to Antriksh for both original submissions; prior reviewer repairs remain separately attributed above. No code from another contributor is being dropped.

Final source-level runtime check: at runtime main 7ac0607ee7884b04a4b225bbdc3cf732c83cb30f, PluginProcessMain passes connection.kernelClient.channel into RemotePluginContext, whose SecretDataProviderProxy and RoleManagementProviderProxy reuse it. This resolves the review's channel-wiring question without claiming packaged-runtime validation. Head 7bf4f6e0d remains unchanged with all nine CI checks green and prior focused tests/quality checks passing. User authorized a serialized squash merge into dev only.

…BAC IPC bridges

BossConsole#53 built a ServerInterceptor (ProcessIdentityInterceptor /
ProcessTokenRegistry) that verifies which process is calling the kernel's
out-of-process IPC surface, independent of anything a request body claims -
but only wired it into PluginUIServiceBridge. The other fourteen service
bridges, documented in that interceptor's own KDoc as "none of them
authenticated today", still trust every caller unconditionally.

SecretServiceBridge and RoleManagementServiceBridge are the two where that
actually matters: the whole password vault (list/search/create/update/
delete/share/unshare) and RBAC (roles, permissions, grants) had no caller
attribution at all. Any out-of-process plugin that can reach the kernel's
local IPC socket could read, mutate or share every secret, or mint and
revoke permission grants, with nothing to tell one plugin's calls from
another's.

Both bridges now require AUTHENTICATED_PROCESS_ID on every RPC - reads
included, not only mutations, since an unattributed read of the vault list
is exactly the exposure this closes - and refuse with PERMISSION_DENIED
before the request ever reaches the underlying provider when there is none.
Every mutating call also logs the verified caller identity, so a
compromised or rogue plugin's activity is attributable after the fact.

Every legitimate out-of-process plugin already carries a valid credential
in production (ChildProcessBootstrap attaches ProcessTokenClientInterceptor
whenever BOSS_PROCESS_TOKEN is set, which ProcessSpawner sets for every
child it spawns), so this closes the gap without changing behavior for any
real plugin.

Tests exercise both bridges over a real gRPC server and real client, mirroring
PluginUIServiceBridgeTest's own harness: an authenticated caller succeeds,
an anonymous one is refused, and - the assertion that actually matters - the
underlying provider is never invoked on a refusal.
@kshivang
kshivang force-pushed the fix/kernel-ipc-bridge-caller-identity branch from 4eea05a to ed41e36 Compare September 11, 2026 09:23
@kshivang
kshivang changed the base branch from main to dev September 11, 2026 09:23
@kshivang

Copy link
Copy Markdown
Contributor

Reopening to retrigger CI after rebase onto current dev.

@kshivang kshivang closed this Sep 11, 2026
@kshivang kshivang reopened this Sep 11, 2026
@kshivang

Copy link
Copy Markdown
Contributor

@Antriksh1984 — reviewed against current dev: this is a solid security fix. Every RPC in SecretServiceBridge and RoleManagementServiceBridge is now gated through authenticatedCallerOrRefuse(rpc), which validates the caller against ProcessIdentityInterceptor.AUTHENTICATED_PROCESS_ID and fails closed with PERMISSION_DENIED plus a warn log for anonymous or unidentifiable callers. The 19 new tests cover the anonymous-refusal paths on both bridges, which is exactly the right regression surface.

Housekeeping note: main is currently ahead of dev (the v9.5.13 release landed on main), so merging this straight to main would have pulled in the release delta. I cherry-picked your single commit onto current dev — ed41e36316, author preserved — and retargeted the PR base to dev. The two bridge files were byte-identical between main and dev, so the cherry-pick was clean and the PR is mergeable with no conflicts.

Validation on the final head: ktlint, detekt, and compile all pass; SecretServiceBridgeTest 9/9, RoleManagementServiceBridgeTest 10/10, and the adjacent bridge tests all green.

@claude Please review current head ed41e36316 against dev for correctness of the caller-identity gating and any regressions in the two bridges.

@claude

claude Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Claude finished @kshivang's task in 4m 53s —— View job


Review: caller-identity gating at ed41e36316 vs dev

  • Read the diff against origin/dev
  • Compare gating against PluginUIServiceBridge
  • Verify every RPC on both bridges is covered
  • Trace the production wiring (server interceptor, spawn token, client interceptor)
  • Review the new tests

Verdict: the gating is correct and I found no regression in either bridge. Findings below are test-coverage and reuse, not correctness defects. I could not run Gradle in this job (./gradlew needs approval here), so I'm relying on your validation for the green suite and on static reading for the rest.


What I verified

Coverage is complete, not approximately complete. secret.proto declares 9 RPCs and SecretServiceBridge has 9 authenticatedCallerOrRefuse( call sites; role_management.proto declares 11 and RoleManagementServiceBridge has 11. Every override gets the gate as its first statement, before anything suspends — which is what makes reading the thread-local io.grpc.Context key safe here.

Fail-closed is genuinely closed. ProcessTokenRegistry.identityFor (ProcessTokenRegistry.kt:63) returns null for absent, blank, unknown and revoked tokens, so a stale credential after a respawn is refused, not just a missing header. The refusal is thrown before provider is touched on every path.

Production wiring holds end to end:

  • KernelBootstrap.kt:471 — BossIpcServer(kernelAddress!!, tokenRegistry), so the interceptor is installed on the server these bridges land on.
  • ProcessSpawner.kt:102 — sets BOSS_PROCESS_TOKEN on every spawn.
  • ChildProcessBootstrap.kt:85 — attaches ProcessTokenClientInterceptor to the kernel channel whenever that token is present.
  • registerPluginServices returns early in MONOLITH (KernelBootstrap.kt:951), and in-process plugins call the providers directly rather than through gRPC — so there is no in-process regression surface at all.

No secret material reaches the log. createSecret logs only rpc + caller; update/delete/share/unshare add secretId and never the website, username or password. That's the right line to draw, and it matches the repo's sanitizeSupabaseFailure posture.

One forward-looking note on the helper: it snapshots AUTHENTICATED_PROCESS_ID, which is correct for unary RPCs. If a streaming RPC ever lands on either bridge, it must use ProcessIdentityInterceptor.CURRENT_IDENTITY instead, the way PluginUIServiceBridge.streamUI does (PluginUIServiceBridge.kt:159) — a snapshot taken at stream open can outlive a revocation. Worth a one-line comment on the helper so the next person doesn't copy it into a stream.


Findings

1. updateSecret is the one gate in this diff with zero test coverage. FakeSecretDataProvider.updateSecretCalls is declared (SecretServiceBridgeTest.kt:195) and never asserted, and there is no updateSecret test of either kind. Delete that one gate line and the suite stays green — which is exactly the property the other 19 tests exist to prevent. Mirror the deleteSecret refusal test. Fix this →

2. Production registers these bridges late; the tests register them at build time. registerPluginServices calls ipcServer.addService(...) after start(), so both bridges go into BossIpcServer's MutableHandlerRegistry fallback (BossIpcServer.kt:49) — whereas PluginUIServiceBridge, the only previously-authenticated bridge, is added before start() and lands in the primary registry. Both new tests use ServerBuilder.addService() directly, i.e. the build-time path.

grpc-java applies ServerBuilder.intercept() interceptors in ServerImpl after method lookup, and lookup consults the primary registry then the fallback — so the fallback path is covered in practice and I expect your green run is real. But nothing in this tree pins it, and this is the first time a fail-closed check depends on it. That asymmetry matters because the failure mode is inverted: if an interceptor/registry-ordering change in a future grpc bump broke it, the old behaviour was "unauthenticated but working" and the new one is "vault and RBAC entirely offline". One test that drives the real BossIpcServer(addr, registry).start() and then addService(SecretServiceBridge(...)) would convert an inference about grpc internals into a regression-tested fact. This is the finding I'd most want addressed. Fix this →

3. Three RBAC refusals are asserted only as "some exception". RoleManagementServiceBridgeTest.kt:180 checks assertFailsWith<StatusException> for getRolePermissions, validateRoleName and validatePermissionName with no status code and no counter — and FakeRoleManagementProvider has no counters for those three at all. That contradicts the class KDoc's own claim ("Every test that expects a refusal also asserts the provider was never called"), and a bare assertFailsWith<StatusException> would pass just as happily on UNAVAILABLE or UNIMPLEMENTED. getRolePermissions is a real read (it returns the role→permission mapping), so it deserves a counter like the others. More broadly, only the two an anonymous caller is refused tests assert Status.Code.PERMISSION_DENIED; the other 17 don't. Worth tightening in both files. Fix this →

4. The revoked-token path is untested. Every refusal test uses "no credential at all". identityFor also returns null for a token the registry no longer holds, which is the post-reapChildren / post-respawn case (KernelBootstrap.kt:420, ProcessTokenRegistry.revoke), and its blast radius differs from an anonymous caller: a live plugin whose process the kernel has reaped loses vault access mid-session. One test per bridge — issue the token, tokenRegistry.revoke(DEFAULT_PROCESS), assert PERMISSION_DENIED and a zero counter — pins the behaviour the audit trail actually leans on.

5. This is now the third copy of authenticatedCallerOrRefuse and NO_IDENTITY. PluginUIServiceBridge.kt:127, SecretServiceBridge.kt:217, RoleManagementServiceBridge.kt:188 differ only in the logger, the LogCategory (UI vs AUTH) and the surfaceId field. Your own "out of scope" list names twelve more bridges — which lands at fifteen copies of one fail-closed decision and fifteen chances for someone to write ?: return instead of ?: throw. One internal fun ComponentLogger.authenticatedCallerOrRefuse(rpc: String, extra: Map<String, Any?> = emptyMap()): String in a KernelCallerIdentity.kt beside the bridges, used by all three, would make the follow-up PRs one-liners each and give the decision a single test surface. I'd do this before the twelve, not after. Fix this →


Two things to confirm outside this repo

The client half of this contract isn't in BossConsole. SecretDataProviderProxy and RoleManagementProviderProxy (plugin-platform/plugin-api-ipc/src/desktopMain/) are constructed nowhere in this repository — per AGENTS.md, boss-ipc publishes upstream for the standalone boss-microkernel-runtime repo. So "no behavior change for any real plugin" holds only if that runtime hands those proxies the channel from ChildProcessConnection.kernelClient (which carries the token interceptor) rather than building its own client. Note BossIpcClient's interceptors parameter defaults to empty, and ChildProcessConnection.connectToService (ChildProcessBootstrap.kt:208) is itself an in-repo example of a helper that returns an uncredentialed client. If the runtime's provider wiring builds a bare BossIpcClient(kernelAddress), every secrets and RBAC call from out-of-process plugins starts failing PERMISSION_DENIED against this host build. I can't establish that from here — it's the one thing I'd check before merge.

Nothing forces a kernel BossIpcServer to have a registry. tokenRegistry defaults to null and its KDoc says that "leaves every service's behaviour exactly as it was" — which stops being true for a server hosting these two bridges, where "no interceptor" now means every call refused, not "unauthenticated as before". A line in that KDoc or at registerPluginServices would keep the next person from shipping a silent total outage. (ChildProcessBootstrap.kt:117's processServer correctly has no registry and hosts neither bridge, so nothing is broken today.)

Lastly, worth folding in: ProcessIdentityInterceptor's KDoc (lines 17-22) still reads "fourteen other service bridges as of writing, none of them authenticated today" and names PluginUIServiceBridge as "the one that currently makes it". That comment is this PR's stated motivation, and after merge it's wrong — it's three of fifteen now. Updating it is the cheapest way to stop the next reader mis-reading the state of the surface.
· fix/kernel-ipc-bridge-caller-identity

…ose the updateSecret gap

- SecretServiceBridgeTest: updateSecret gets its own anonymous-refusal
  test, matching the vault mutations it already covered.
- SecretServiceBridgeTest: a service registered on an already-started
  server (BossIpcServer's late-service fallback registry, as
  KernelBootstrap.registerPluginServices does) is still gated by
  ProcessIdentityInterceptor - anonymous calls are refused before the
  vault is reached, token calls go through. This pins the registration
  order (fallbackHandlerRegistry before intercept) the production
  path relies on.
- KDoc on both authenticatedCallerOrRefuse helpers: the
  AUTHENTICATED_PROCESS_ID snapshot is correct for unary RPCs; a
  streaming bridge must read CURRENT_IDENTITY instead (see
  PluginUIServiceBridge.streamUI).
@kshivang

Copy link
Copy Markdown
Contributor

@Antriksh1984 Follow-up after the Claude review: all three coverage findings are now addressed in ba463eba9.

What changed

  1. updateSecret gap closed — SecretServiceBridgeTest now has updateSecret refuses an anonymous caller and never reaches the vault, matching the other seven vault mutations (the test asserts the provider was never invoked, not just that the RPC was refused).
  2. Late-registration pin — new test a bridge registered after start is still gated by the interceptor: a SecretServiceBridge registered on an already-started server (i.e. through BossIpcServer's late-service fallback registry, exactly what KernelBootstrap.registerPluginServices does) is still gated by ProcessIdentityInterceptor. Anonymous getUserSecrets is refused with PERMISSION_DENIED before reaching the vault; a token-bearing call goes through. The test builds the server in the same order as production (fallbackHandlerRegistry before intercept). The registry stand-in mirrors grpc-util's MutableHandlerRegistry lookup semantics (keyed by full method name, as grpc 1.84 consults the fallback registry).
  3. Streaming caveat documented — KDoc on both authenticatedCallerOrRefuse helpers (secrets + RBAC) now states the AUTHENTICATED_PROCESS_ID snapshot is correct for unary RPCs only; a streaming bridge must read CURRENT_IDENTITY (as PluginUIServiceBridge.streamUI does), so a mid-stream revocation is honoured.

Validation

  • :composeApp:desktopTest for both bridge test classes: 11/11 SecretServiceBridgeTest + 10/10 RoleManagementServiceBridgeTest pass, including both new tests.
  • :composeApp:detekt + :composeApp:ktlintCheck clean.

The gating logic itself is unchanged — this pass only adds the missing regression coverage and the doc note. @claude please re-review at ba463eba9.

@claude

claude Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Claude finished @kshivang's task in 3m 24s —— View job


Re-review: ba463eba9 (coverage + doc pass)

  • Read the incremental diff ed41e36..ba463eb
  • Verify the updateSecret test closes the gate gap
  • Scrutinise the late-registration pin against production's wiring
  • Check the streaming KDoc note
  • Re-confirm no gating regression in either bridge

Verdict: the gating is unchanged and still correct, and two of the three findings are genuinely closed. One correction to the summary above: finding 3 is not in this commit. ba463eb touches three files - the two bridge KDocs and SecretServiceBridgeTest.kt. RoleManagementServiceBridgeTest.kt is byte-unchanged since ed41e36, so the RBAC assertion tightening described as addressed is not present. Details below. (I could not run Gradle in this job - ./gradlew needs approval here - so the green suite is on your validation; everything below is static.)


Confirmed closed

1. updateSecret (SecretServiceBridgeTest.kt:166). Matches the deleteSecret shape, asserts provider.updateSecretCalls == 0, and that counter is no longer dead. Delete the gate at SecretServiceBridge.kt:144 and this now fails. Good.

2. Late registration (SecretServiceBridgeTest.kt:177). This does the thing that mattered: it builds a real server with fallbackHandlerRegistry(...) then intercept(...) - the same order as BossIpcServer.kt:69-70 - and proves grpc applies a builder-level interceptor to a method resolved through the fallback registry. That converts my inference about ServerImpl internals into a test a future grpc bump has to keep green, and it asserts both directions (PERMISSION_DENIED + zero counter for anonymous, success + counter 1 for the token-bearing stub). The SingleServiceRegistry stand-in is justified on its stated grounds - libs.grpc.util is implementation in modules/boss-ipc/build.gradle.kts:38, so io.grpc.util.MutableHandlerRegistry really is not on composeApp's compile classpath.

3. Streaming caveat. Accurate, and on both helpers. Exactly the note I wanted, in the place the next person copies from.


Findings

1. Finding 3 from the last round is still open, and the class KDoc still overstates. RoleManagementServiceBridgeTest.kt:180 (getRolePermissions and name validation refuse an anonymous caller) still asserts a bare assertFailsWith<StatusException> three times with no status code and no counters at all - FakeRoleManagementProvider has eight counters and getRolePermissions, validateRoleName and validatePermissionName are not among them (getRolePermissions at the bottom of the fake increments nothing). So the class KDoc's "Every test that expects a refusal also asserts FakeRoleManagementProvider was never called" is still false for the one RBAC read RPC, which is the same exposure updateSecret was. A bare assertFailsWith<StatusException> would also pass on UNAVAILABLE or UNIMPLEMENTED; across both files only 2 of 21 tests pin Status.Code.PERMISSION_DENIED. Fix this →

2. The new test rotates the token the shared authenticated stub is holding. SecretServiceBridgeTest.kt:201 calls tokenRegistry.issue(DEFAULT_PROCESS) for the id setUp already issued for, and ProcessTokenRegistry.issue (ProcessTokenRegistry.kt:50-55) replaces and invalidates the previous token by design. Harmless today only because nothing in that test body touches authenticated afterwards - but it is a trap set for the next person: add one assertion using authenticated below line 215 and it fails PERMISSION_DENIED for a reason that has nothing to do with gating, which reads as a gating bug. Issue the late credential for a distinct id ("$DEFAULT_PROCESS.late").

Worth noting this test has already constructed the revoked-token case (my finding 4) without asserting it - the setUp token is invalid from line 201 onward. A three-line test on top of that distinct-id fix pins the case a reaped-but-live plugin actually hits. Fix this →

3. Residual on the late-registration pin: BossIpcServer's own wiring is still unpinned. The test reproduces production's shape by hand, so it will not notice if BossIpcServer itself changes - reorder fallbackHandlerRegistry and intercept at BossIpcServer.kt:69-70, or let the tokenRegistry?.let go missing, and the new test stays green while the vault goes anonymous or offline. That is avoidable without the classpath problem the stand-in exists for: BossIpcServer is on composeApp's compile classpath (composeApp/build.gradle.kts:987) and encapsulates the MutableHandlerRegistry, so driving the real one needs no new import - BossIpcServer(addr, tokenRegistry).start(), then .addService(SecretServiceBridge(provider)), with IpcAddressResolver.configureChannelBuilder(addr) for the client. The socket lands under BOSS_DATA_DIR/~/.boss/ipc, and composeApp's per-task user.home redirect (AGENTS.md) keeps that out of a developer's real directory. Not a blocker - the grpc-level fact is the one that was unproven - but it is a strictly stronger test for about the same number of lines. Fix this →

4. Nit: an orphaned KDoc. SecretServiceBridgeTest.kt:249 - /** Records every call so a refusal test can assert the vault was never actually reached. */ - used to sit on FakeSecretDataProvider and now dangles above SingleServiceRegistry's own KDoc, documenting nothing. Move it back down to line 269. While there: lateServer.shutdownNow() is the last statement of the test body rather than in a finally, so a failed assertion leaks a bound server for the rest of the test worker. Fix this →


Two doc facts in boss-ipc that this PR makes wrong

Both were in the last review, neither is claimed as addressed, and both are one-line edits in the module this PR's motivation quotes:

  • ProcessIdentityInterceptor.kt:19-22 still reads "fourteen other service bridges as of writing, none of them authenticated today" and names PluginUIServiceBridge as "the one that currently makes it". After merge it is three of fifteen. This is the comment the PR description quotes as its own justification.
  • BossIpcServer.kt:31-38: the tokenRegistry KDoc says null "installs no interceptor and leaves every service's behaviour exactly as it was". That is no longer true for a server hosting these two bridges, where no interceptor means every vault and RBAC call refused - a silent total outage rather than the pre-Remote UI: StreamUI has no plugin identity check — one plugin can bind another's surface #53 status quo. One clause there (or at registerPluginServices) is the cheapest guard against the next kernel server being built without a registry.

Fix this →

Also still open and unclaimed: finding 5 (three copies of authenticatedCallerOrRefuse / NO_IDENTITY, now with a three-link KDoc chain where RBAC mirrors secrets which mirrors UI). Worth doing before the twelve follow-up bridges rather than after, but not in this PR's way.

Bottom line

No correctness defect in the gating, and no regression introduced by ba463eb - the two pins are real and the streaming note is the right one. Findings 2 and 4 above are small enough to fold in here; finding 1 is the one I would not leave, since the summary reports it as done and the RBAC read RPC is the same class of exposure updateSecret was.
· fix/kernel-ipc-bridge-caller-identity

…en coverage, fix stale KDocs

RoleManagementServiceBridgeTest: getRolePermissions, validateRoleName and
validatePermissionName were asserted only as "some StatusException", with
no counters on FakeRoleManagementProvider at all - a bare
assertFailsWith<StatusException> would pass just as happily on
UNAVAILABLE or UNIMPLEMENTED, contradicting the class's own KDoc claim
that every refusal test proves the provider was never reached. Added the
three missing counters and now assert PERMISSION_DENIED plus a zero
counter on every refusal in both bridge test files, not just the two
"an anonymous caller is refused" tests.

SecretServiceBridgeTest: the late-registration test called
tokenRegistry.issue(DEFAULT_PROCESS) for the same id setUp already
issued for - issue() replaces and invalidates the previous token by
design, so this silently killed the credential the shared `authenticated`
stub carries for the rest of the class. Now issues a distinct id.
lateServer teardown moved into a finally so a failed assertion doesn't
leak a bound server. Moved an orphaned KDoc comment back onto
FakeSecretDataProvider (SingleServiceRegistry's insertion had left it
dangling above the wrong class).

Added a revoked-token test to both files: identityFor returns null for a
token the registry no longer holds, the post-reapChildren/post-respawn
case, previously untested - every existing refusal test only covered
"no credential at all", not "a credential that was live and got pulled".

Two stale KDoc facts fixed in boss-ipc: ProcessIdentityInterceptor's
comment named PluginUIServiceBridge as the only bridge checking identity
(now three of fifteen, with SecretServiceBridge and
RoleManagementServiceBridge named); BossIpcServer's tokenRegistry KDoc
claimed a null registry "leaves every service's behaviour exactly as it
was", which stopped being true for a server hosting either of the two
fail-closed bridges - a null registry there means every call refused,
not merely unauthenticated as before.
@Antriksh1984

Copy link
Copy Markdown
Contributor Author

Addressed finding 1 (still fully open) and both items from the follow-up review, pushed on `081bf2a1`:

Finding 1 - RBAC refusal assertions. Added the three missing counters (`getRolePermissionsCalls`, `validateRoleNameCalls`, `validatePermissionNameCalls`) to `FakeRoleManagementProvider` and now assert both `Status.Code.PERMISSION_DENIED` and a zero counter on every refusal in both `RoleManagementServiceBridgeTest` and `SecretServiceBridgeTest` - not just the two `an anonymous caller is refused` tests. 21/21 refusal assertions now pin the actual status code.

Follow-up finding 2 - the shared token bug + revoked-token gap. The late-registration test's `tokenRegistry.issue(DEFAULT_PROCESS)` was silently invalidating the `authenticated` stub's credential for the rest of the class (harmless today only because nothing after it used that stub). Now issues a distinct id. Also added a real revoked-token test to both files: issue a token, confirm it works, `tokenRegistry.revoke(...)`, assert `PERMISSION_DENIED` and the counter stays put - the post-reapChildren/post-respawn case, previously only constructed by accident and never asserted.

Follow-up finding 4 (nit). Moved the orphaned `FakeSecretDataProvider` KDoc back onto the class it describes, and `lateServer` teardown is now in a `finally`.

Both stale KDoc facts. `ProcessIdentityInterceptor`'s comment now says "twelve other service bridges... none of them authenticated" and names all three that do check identity (`PluginUIServiceBridge`, `SecretServiceBridge`, `RoleManagementServiceBridge`). `BossIpcServer`'s `tokenRegistry` KDoc now says explicitly that a null registry stops being a no-op once a fail-closed bridge is hosted on it - every call refused, not "unauthenticated as before."

Not addressed this round (as you framed it, reasonable to leave for now):

  • Finding 3 (strengthening the late-registration test to drive the real `BossIpcServer` instead of the `SingleServiceRegistry` stand-in) - not a blocker per your own read, and I focused on the coverage gaps first.
  • Finding 5 (extracting the now-triplicated `authenticatedCallerOrRefuse`/`NO_IDENTITY`) - you said explicitly "not in this PR's way," so leaving for a dedicated follow-up before the twelve remaining bridges.
  • The client-side proxy wiring question in `boss-microkernel-runtime` - can't verify from this repo.

Happy to pick up finding 3 or the DRY extraction as a follow-up if you'd like them closed rather than left as known gaps.

Validation: `compileKotlinDesktop`/`compileTestKotlinDesktop`/`boss-ipc:compileKotlin` clean, both bridge test suites green (`SecretServiceBridgeTest` 12/12, `RoleManagementServiceBridgeTest` 11/11 including the two new revoked-token tests), `ktlintCheck`/`detekt` clean on every file this commit touches.

@kshivang

Copy link
Copy Markdown
Contributor

@Antriksh1984 Status at 081bf2a13 - validated locally, CI fully green (run 34591841240). One record correction from my side, then the finding-by-finding state.

Record correction (my earlier comment 5633174606). I wrote that "all three coverage findings" were addressed in ba463eba9. That was an overstatement: the RBAC refusal-assertion finding (the getRolePermissions/validateRoleName/validatePermissionName "some exception" gap) was not closed by ba463eba9 - it is closed by your 081bf2a13, which adds the three missing counters and pins PERMISSION_DENIED on every refusal in both files. Also, ba463eba9's late-registration test had a real bug your commit caught and fixed: it called tokenRegistry.issue(DEFAULT_PROCESS) for the same id setUp already issued, silently invalidating the shared authenticated stub's credential (your distinct-id fix plus the finally teardown resolves it). My own parallel fix for these same findings (unpushed local commit 7d5d9686f) was discarded as redundant once your push landed on the same base - credit stays yours.

Validation on 081bf2a13 (this machine, before the CI run finished):

  • SecretServiceBridgeTest 12/12 and RoleManagementServiceBridgeTest 12/12 pass, including the two new revoked-token tests.
  • :composeApp:ktlintCheck + :composeApp:detekt + :boss-ipc:ktlintCheck + :boss-ipc:detekt clean.
  • CI: all 9 checks pass on 081bf2a13.

Finding state at head:

  • updateSecret coverage - closed (test present with PERMISSION_DENIED pin and zero-provider-counter assertion).
  • RBAC weak assertions - closed by 081bf2a13 (21/21 refusal assertions now pin the status code).
  • Late-registration asymmetry - covered between two tests: the kernel-side pin exercises the fallback-registry path with the interceptor (anonymous refused, token-bearing call succeeds), and the pre-existing boss-ipc LateServiceRegistrationTest drives the real BossIpcServer with addService() after start(). Residual gap, exactly as you scoped it: no single test combines the real BossIpcServer + registry + a late-registered fail-closed bridge. Fine to track as the follow-up you offered.
  • Stale KDocs (interceptor + tokenRegistry) - closed in 081bf2a13. The null-registry rewrite is the right framing.
  • Triplicated authenticatedCallerOrRefuse/NO_IDENTITY - deferred by mutual agreement; not in this PR's way.
  • Optional nits, not blocking: (1) the interceptor KDoc's "twelve other service bridges" undercounts by one - the kernel registers 16 bridges on its ipcServer (15 in KernelBootstrap.registerPluginServices plus PluginUIServiceBridge at KernelBootstrap.kt:475), so 13 are unauthenticated today. (2) The first, untouched lines of both rewritten KDocs still contain the pre-existing em-dashes that AGENTS.md bans in prose. Leave both or fix in a sweep - your call.

No author-side blockers remain that I can see; what's left on this PR is review sign-off. @claude please re-review at 081bf2a13.

@claude

claude Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Claude finished @kshivang's task in 2m 34s —— View job


Re-review: 081bf2a13 (assertion tightening, revoked-token coverage, KDoc fixes)

  • Read the incremental diff ba463eb..081bf2a1
  • Verify the RBAC counters and PERMISSION_DENIED pins
  • Verify the distinct-id fix and the revoked-token tests
  • Check both rewritten KDocs (including the bridge-count nit)
  • Re-confirm no gating regression

Verdict: no correctness defect, no regression, and every finding I raised that was claimed as closed is genuinely closed. 081bf2a1 touches four files - the two test classes and the two boss-ipc KDocs - and the two bridge implementations are byte-unchanged since ed41e36, so the gating logic I signed off on twice is untouched. From my side this is done. (Gradle needs approval in this job, so the green suite is on your validation and CI run 34591841240; everything below is static.)


Confirmed closed

RBAC weak assertions. FakeRoleManagementProvider now increments on all three previously-silent methods (getRolePermissionsCalls, validateRoleNameCalls, validatePermissionNameCalls, RoleManagementServiceBridgeTest.kt:273-275), and the test at :180 asserts PERMISSION_DENIED plus a zero counter for each. The class KDoc's claim ("Every refusal test also asserts FakeRoleManagementProvider was never called") is now true rather than aspirational, and getRolePermissions - the one RBAC read that returns real mapping data - is covered the way updateSecret ended up being. Across both files I count every refusal pinning Status.Code.PERMISSION_DENIED; nothing is left as a bare assertFailsWith<StatusException>.

The shared-token trap. SecretServiceBridgeTest.kt:213 now issues for "$DEFAULT_PROCESS.late", and the comment above it explains why rather than just what - which is the part that stops it being reintroduced. lateServer teardown is in a finally, and the orphaned FakeSecretDataProvider KDoc is back on its class (:319).

Revoked-token coverage. Both new tests do the right three steps in the right order: prove the credential works (counter 1), revoke, then assert PERMISSION_DENIED and that the counter did not move - so a refusal that still reached the provider fails. They use a distinct "$DEFAULT_PROCESS.revocable" id, so they do not repeat the bug they were written alongside. This is the case ProcessTokenRegistry.identityFor's own KDoc calls out ("a token a later issue or revoke has since invalidated") and it was the one branch of that contract with no test.

Both KDocs. BossIpcServer's tokenRegistry rewrite is the right framing: it keeps the "no-op for a service that never checks identity" claim, which is still true and still the reason every child processServer passes null, and adds the new failure mode as a consequence of hosting a fail-closed bridge rather than a blanket warning. The closing sentence ("Any kernel server hosting one of those bridges must be constructed with a real registry") is the actionable line. ProcessIdentityInterceptor now names all three authenticating bridges.


Two notes on your optional nits

(1) Your count is right, and the undercount predates this PR. 16 bridge classes exist in ai/rever/boss/kernel/services/, and all 16 reach the kernel ipcServer - 15 through KernelBootstrap.registerPluginServices (:954-972) plus PluginUIServiceBridge at :475. Three authenticate, so thirteen do not. The pre-existing "fourteen" implied a total of fifteen, so it was already off by one and 081bf2a1 carried the error down by subtracting two from the wrong number. The PR description's own out-of-scope list names exactly thirteen bridges, which is the corroboration. One word. Fix this →

(2) The em-dash one is a non-issue by the repo's own rule - I would not sweep it. AGENTS.md's enforcement table is explicit: *.md/*.html are gated in build.yml, string literals in code are convention-upheld-by-review, and code comments are "out of scope entirely." Every em-dash you spotted is in a KDoc. Sweeping them would touch untouched lines in two modules to satisfy a rule that deliberately exempts them, and it would make the next person think the rule covers comments. Leave them.


One optional strengthening (not a blocker, and not something I'd hold the PR for)

The new streaming caveat on both helpers is the right note in the right place, but nothing enforces it. If someone adds a streaming RPC to either bridge and copies the adjacent unary pattern, the KDoc is the only thing standing in the way, and a snapshot taken at stream open survives a revocation the whole stream long - which is precisely the case the two new revoked-token tests prove matters.

That is cheaply pinnable without a new dependency: assert over SecretServiceBridge(provider).bindService().methods that every ServerMethodDefinition.methodDescriptor.type is MethodDescriptor.MethodType.UNARY. One assertion per bridge, and it fails the day the assumption the helper is documented against stops holding. Worth folding into the same follow-up as the DRY extraction (finding 5), since a shared KernelCallerIdentity.kt makes it one test instead of fifteen. Fix this →


Still open, all agreed as out of this PR's way

  • Real BossIpcServer in the late-registration test. Your read is right that this is now covered across two tests rather than one: the kernel-side test pins the grpc-level fact (builder interceptor reaches a fallback-registry method, in production's fallbackHandlerRegistry-then-intercept order), and boss-ipc's LateServiceRegistrationTest pins BossIpcServer.addService() after start(). The residual is exactly as you scoped it: no single test has the real server and a registry and a fail-closed bridge, so reordering BossIpcServer.kt:69-70 or dropping the tokenRegistry?.let leaves both tests green. Tracking as the follow-up is fine.
  • Triplicated authenticatedCallerOrRefuse / NO_IDENTITY. Now a three-link KDoc chain (RBAC mirrors secrets mirrors UI), which is a reasonable interim. Still worth doing before the thirteen, not after.
  • The client half in boss-microkernel-runtime. Unchanged and still unverifiable from here - SecretDataProviderProxy and RoleManagementProviderProxy are constructed nowhere in this repo, and BossIpcClient's interceptors defaults to empty. If that runtime builds a bare BossIpcClient(kernelAddress) for provider wiring rather than reusing ChildProcessConnection.kernelClient, out-of-process secrets and RBAC calls start failing PERMISSION_DENIED against this host build. It is the one pre-merge check that lives outside this repository.

Bottom line

The gating is correct, coverage now matches what the class KDocs claim, and the two KDoc facts this PR invalidated are repaired. The only thing I would still change here is the one-word count fix; everything else is either a non-issue by AGENTS.md's own rule or already agreed as a follow-up.
· fix/kernel-ipc-bridge-caller-identity

…r KDoc

The kernel registers 16 bridges on its ipcServer (15 in
KernelBootstrap.registerPluginServices plus PluginUIServiceBridge at
KernelBootstrap.kt:475); three authenticate, so thirteen are
unauthenticated, not twelve. Per the Claude review's one-word count fix.
@kshivang

Copy link
Copy Markdown
Contributor

Follow-up to the re-review: applied the one change the bottom line called out - the KDoc count in ProcessIdentityInterceptor.kt is now "thirteen other service bridges" (7bf4f6e0d). The em-dash point stands closed as a non-issue per AGENTS.md's own table (code comments out of scope), so I did not sweep the untouched lines.

Validation at 7bf4f6e0d: KDoc-only change; :boss-ipc:ktlintCheck + :boss-ipc:detekt clean, CI run in progress.

The agreed follow-ups (real-BossIpcServer late-registration test, the DRY extraction of authenticatedCallerOrRefuse/NO_IDENTITY before the remaining bridges, the optional all-UNARY method pin, and the boss-microkernel-runtime client-side check) are all recorded in the review thread. Review-wise nothing else is outstanding on this head.

@kshivang
kshivang merged commit 109fb08 into risa-labs-inc:dev Sep 13, 2026
9 checks passed
kshivang added a commit that referenced this pull request Sep 13, 2026
…BAC IPC bridges (#505)

* fix(kernel): require a verified caller identity for the secrets and RBAC IPC bridges

BossConsole#53 built a ServerInterceptor (ProcessIdentityInterceptor /
ProcessTokenRegistry) that verifies which process is calling the kernel's
out-of-process IPC surface, independent of anything a request body claims -
but only wired it into PluginUIServiceBridge. The other fourteen service
bridges, documented in that interceptor's own KDoc as "none of them
authenticated today", still trust every caller unconditionally.

SecretServiceBridge and RoleManagementServiceBridge are the two where that
actually matters: the whole password vault (list/search/create/update/
delete/share/unshare) and RBAC (roles, permissions, grants) had no caller
attribution at all. Any out-of-process plugin that can reach the kernel's
local IPC socket could read, mutate or share every secret, or mint and
revoke permission grants, with nothing to tell one plugin's calls from
another's.

Both bridges now require AUTHENTICATED_PROCESS_ID on every RPC - reads
included, not only mutations, since an unattributed read of the vault list
is exactly the exposure this closes - and refuse with PERMISSION_DENIED
before the request ever reaches the underlying provider when there is none.
Every mutating call also logs the verified caller identity, so a
compromised or rogue plugin's activity is attributable after the fact.

Every legitimate out-of-process plugin already carries a valid credential
in production (ChildProcessBootstrap attaches ProcessTokenClientInterceptor
whenever BOSS_PROCESS_TOKEN is set, which ProcessSpawner sets for every
child it spawns), so this closes the gap without changing behavior for any
real plugin.

Tests exercise both bridges over a real gRPC server and real client, mirroring
PluginUIServiceBridgeTest's own harness: an authenticated caller succeeds,
an anonymous one is refused, and - the assertion that actually matters - the
underlying provider is never invoked on a refusal.

* test(kernel): pin the identity gate on late-registered bridges and close the updateSecret gap

- SecretServiceBridgeTest: updateSecret gets its own anonymous-refusal
  test, matching the vault mutations it already covered.
- SecretServiceBridgeTest: a service registered on an already-started
  server (BossIpcServer's late-service fallback registry, as
  KernelBootstrap.registerPluginServices does) is still gated by
  ProcessIdentityInterceptor - anonymous calls are refused before the
  vault is reached, token calls go through. This pins the registration
  order (fallbackHandlerRegistry before intercept) the production
  path relies on.
- KDoc on both authenticatedCallerOrRefuse helpers: the
  AUTHENTICATED_PROCESS_ID snapshot is correct for unary RPCs; a
  streaming bridge must read CURRENT_IDENTITY instead (see
  PluginUIServiceBridge.streamUI).

* test(kernel): tighten RBAC/secret refusal assertions, add revoked-token coverage, fix stale KDocs

RoleManagementServiceBridgeTest: getRolePermissions, validateRoleName and
validatePermissionName were asserted only as "some StatusException", with
no counters on FakeRoleManagementProvider at all - a bare
assertFailsWith<StatusException> would pass just as happily on
UNAVAILABLE or UNIMPLEMENTED, contradicting the class's own KDoc claim
that every refusal test proves the provider was never reached. Added the
three missing counters and now assert PERMISSION_DENIED plus a zero
counter on every refusal in both bridge test files, not just the two
"an anonymous caller is refused" tests.

SecretServiceBridgeTest: the late-registration test called
tokenRegistry.issue(DEFAULT_PROCESS) for the same id setUp already
issued for - issue() replaces and invalidates the previous token by
design, so this silently killed the credential the shared `authenticated`
stub carries for the rest of the class. Now issues a distinct id.
lateServer teardown moved into a finally so a failed assertion doesn't
leak a bound server. Moved an orphaned KDoc comment back onto
FakeSecretDataProvider (SingleServiceRegistry's insertion had left it
dangling above the wrong class).

Added a revoked-token test to both files: identityFor returns null for a
token the registry no longer holds, the post-reapChildren/post-respawn
case, previously untested - every existing refusal test only covered
"no credential at all", not "a credential that was live and got pulled".

Two stale KDoc facts fixed in boss-ipc: ProcessIdentityInterceptor's
comment named PluginUIServiceBridge as the only bridge checking identity
(now three of fifteen, with SecretServiceBridge and
RoleManagementServiceBridge named); BossIpcServer's tokenRegistry KDoc
claimed a null registry "leaves every service's behaviour exactly as it
was", which stopped being true for a server hosting either of the two
fail-closed bridges - a null registry there means every call refused,
not merely unauthenticated as before.

* docs(ipc): correct the unauthenticated bridge count in the interceptor KDoc

The kernel registers 16 bridges on its ipcServer (15 in
KernelBootstrap.registerPluginServices plus PluginUIServiceBridge at
KernelBootstrap.kt:475); three authenticate, so thirteen are
unauthenticated, not twelve. Per the Claude review's one-word count fix.

---------

Co-authored-by: kshivang <kshivang@users.noreply.github.com>
Antriksh1984 added a commit to Antriksh1984/BossConsole that referenced this pull request Sep 14, 2026
Every ActiveTabsService RPC was reachable by any process able to open a
connection to the kernel IPC server, not only the plugins the host itself
loaded. watchActiveTabs disclosed every open tab's title and URL across
every window with no credential at all, and createBrowserTab could force
the browser to navigate to an arbitrary URL; selectTab/closeTab could
manipulate what the user is looking at.

All seven RPCs now require a verified caller identity, matching the
pattern PR risa-labs-inc#505 established for the Secret Service: unary RPCs call
authenticatedCallerOrRefuse, and the watchActiveTabs stream captures
ProcessIdentityInterceptor.CURRENT_IDENTITY synchronously in the method
body (before the flow is collected, on the calling thread the interceptor's
context is current on) and re-checks it before every emission, so a
revoked caller's stream is refused before an already-held handle could
receive a value it should no longer see. Refusals are logged with a
bind/revocation stage tag, and the three mutating RPCs (selectTab,
createBrowserTab, closeTab) now record an attributed audit line - caller
and RPC name only, no URLs or titles.

Tested with a real gRPC server plus ProcessIdentityInterceptor: every RPC
is refused with no credential and never reaches the provider, an
authenticated caller can watch/read/select/create/close tabs, and a
revoked caller's live watch stream terminates with PERMISSION_DENIED
before receiving a value that arrived after revocation.
Antriksh1984 added a commit to Antriksh1984/BossConsole that referenced this pull request Sep 14, 2026
…on bridge

Every RunConfigurationService RPC was reachable by any process able to
open a connection to the kernel IPC server. watchDetectedConfigurations
disclosed every auto-detected run configuration (file paths, commands,
arguments, environment variables) with no credential, and execute was an
unauthenticated arbitrary-command-execution primitive: ExecuteConfigRequest
carries a full command, argument string, working directory and environment
map that RunConfigurationDataProvider.execute hands straight to
RunExecutionService, whose own KDoc says what that does - "opens a
terminal and runs the command."

All six RPCs now require a verified caller identity, matching the pattern
PR risa-labs-inc#505 established for the Secret Service. execute gets a second,
narrower guard on top of identity: it does not run what the caller sent.
It takes only the requested id, looks up the matching entry in the
provider's own detectedConfigurations, and executes that provider-held
copy - so a request naming a real id but carrying a tampered command,
or naming an id outside the provider's current set (including one from
before a rescan, since scanning clears the list and mints fresh ids),
is refused with PERMISSION_DENIED before RunExecutionService ever sees
it. A blank id is refused outright rather than matching a stray
blank-id entry.

Tested with a real gRPC server: every RPC is refused with no credential;
an authenticated caller can watch/scan/clear errors; execute runs the
provider's own stored command even when the request carries a tampered
one; an unknown or blank id is refused and never reaches
RunExecutionService; a revoked caller's live watch stream terminates
before a later snapshot; and an id observed before a rescan is refused
both during and after it.
kshivang pushed a commit that referenced this pull request Sep 14, 2026
…dge (#646)

Every ActiveTabsService RPC was reachable by any process able to open a
connection to the kernel IPC server, not only the plugins the host itself
loaded. watchActiveTabs disclosed every open tab's title and URL across
every window with no credential at all, and createBrowserTab could force
the browser to navigate to an arbitrary URL; selectTab/closeTab could
manipulate what the user is looking at.

All seven RPCs now require a verified caller identity, matching the
pattern PR #505 established for the Secret Service: unary RPCs call
authenticatedCallerOrRefuse, and the watchActiveTabs stream captures
ProcessIdentityInterceptor.CURRENT_IDENTITY synchronously in the method
body (before the flow is collected, on the calling thread the interceptor's
context is current on) and re-checks it before every emission, so a
revoked caller's stream is refused before an already-held handle could
receive a value it should no longer see. Refusals are logged with a
bind/revocation stage tag, and the three mutating RPCs (selectTab,
createBrowserTab, closeTab) now record an attributed audit line - caller
and RPC name only, no URLs or titles.

Tested with a real gRPC server plus ProcessIdentityInterceptor: every RPC
is refused with no credential and never reaches the provider, an
authenticated caller can watch/read/select/create/close tabs, and a
revoked caller's live watch stream terminates with PERMISSION_DENIED
before receiving a value that arrived after revocation.
kshivang pushed a commit that referenced this pull request Sep 14, 2026
…dge (#646)

Every ActiveTabsService RPC was reachable by any process able to open a
connection to the kernel IPC server, not only the plugins the host itself
loaded. watchActiveTabs disclosed every open tab's title and URL across
every window with no credential at all, and createBrowserTab could force
the browser to navigate to an arbitrary URL; selectTab/closeTab could
manipulate what the user is looking at.

All seven RPCs now require a verified caller identity, matching the
pattern PR #505 established for the Secret Service: unary RPCs call
authenticatedCallerOrRefuse, and the watchActiveTabs stream captures
ProcessIdentityInterceptor.CURRENT_IDENTITY synchronously in the method
body (before the flow is collected, on the calling thread the interceptor's
context is current on) and re-checks it before every emission, so a
revoked caller's stream is refused before an already-held handle could
receive a value it should no longer see. Refusals are logged with a
bind/revocation stage tag, and the three mutating RPCs (selectTab,
createBrowserTab, closeTab) now record an attributed audit line - caller
and RPC name only, no URLs or titles.

Tested with a real gRPC server plus ProcessIdentityInterceptor: every RPC
is refused with no credential and never reaches the provider, an
authenticated caller can watch/read/select/create/close tabs, and a
revoked caller's live watch stream terminates with PERMISSION_DENIED
before receiving a value that arrived after revocation.
Antriksh1984 added a commit to Antriksh1984/BossConsole that referenced this pull request Sep 14, 2026
…on bridge

Every RunConfigurationService RPC was reachable by any process able to
open a connection to the kernel IPC server. watchDetectedConfigurations
disclosed every auto-detected run configuration with no credential, and
execute was an unauthenticated arbitrary-command-execution primitive:
ExecuteConfigRequest carries a full command, argument string, working
directory and environment map that RunConfigurationDataProvider.execute
hands straight to RunExecutionService, whose own KDoc says what that
does - "opens a terminal and runs the command."

All six RPCs now require a verified caller identity, matching the
pattern PR risa-labs-inc#505 established for the Secret Service. execute does not
run what the caller sent: it takes only the requested id, looks up the
matching entry in the provider's own detectedConfigurations, and
executes that provider-held copy - so a request naming a real id but
carrying a tampered command, or naming an id outside the provider's
current set (including a blank id, or one from before a rescan), is
refused with PERMISSION_DENIED before RunExecutionService ever sees it.

The client-side proxy (RunConfigDataProviderProxy) previously caught
every exception from a refused execute and discarded it silently, so a
Run click during or just after a rescan resolved to nothing on either
side - no terminal, no error, only a host-log line nobody was looking
at. execute now surfaces a client-visible refusal through lastError,
the one signal RunConfigurationDataProvider has and that the run panel
already renders, kept separate from the host's own scan-error channel
so the two lifetimes don't clobber each other: a local execution error
clears on a successful retry or an explicit clearError(), a host error
is preserved underneath it and restored once the local one clears, and
the message stays generic rather than claiming a specific cause (stale
id vs. a revoked token vs. a transport fault) it cannot actually
distinguish safely. Cancellation now propagates untouched from all
three RPCs rather than being swallowed by a blanket catch.

Tested with a real gRPC server against the bridge (8 cases: identity
gating on all six RPCs, tamper resistance, unknown/blank id refusal,
unary and mid-stream revocation, refusal during and after a rescan) and
against the proxy with a fake service (9 cases: refused vs. successful
execute, host/local error independence, dismiss and retry clearing,
transport failure not claiming a stale id, and cancellation propagating
from all three RPCs without publishing a spurious error).
Mihir-Rabari added a commit to Mihir-Rabari/BossConsole that referenced this pull request Sep 22, 2026
…d service bridges

NotificationServiceBridge, ContextMenuServiceBridge, DirectoryPickerServiceBridge,
PanelEventServiceBridge and PerformanceServiceBridge accepted any authenticated
process with no per-call attribution, while their siblings already enforce the
documented verified-caller-identity pattern (BossConsole#53, introduced for the
Secret Service in PR risa-labs-inc#505). A spawned process could spoof toasts, inject context-menu
registrations, drive the native directory dialog, rearrange panels and forge
performance mutations with nothing in the log to attribute them to.

- Add the sibling AUTHENTICATED_PROCESS_ID helper (mirror + KDoc citing risa-labs-inc#53/risa-labs-inc#505) to
  all five bridges; unary RPCs refuse with PERMISSION_DENIED when no verified identity
  is present, even with no interceptor in front of them.
- Performance watchers capture CURRENT_IDENTITY at subscription and revalidate the
  caller on every emission, the same mid-stream revocation shape LogServiceBridge uses.
- ContextMenuServiceBridge keeps its passive acknowledge-and-drop contract (issue risa-labs-inc#30)
  for verified callers; only unattributed callers change behaviour.
- New UnattributedBridgesIdentityTest: all five services over a real gRPC server with
  the production interceptor (no credential -> UNAUTHENTICATED, providers never
  reached), authenticated calls reach each provider, and direct calls with no identity
  context fail closed with PERMISSION_DENIED even without the interceptor.
- ContextMenuServiceBridgeTest now pins the refusal half of the risa-labs-inc#53 contract directly.

Gates: ktlintCheck + detekt + :composeApp:desktopTest --tests "ai.rever.boss.kernel.services.*"
in one invocation - 13 suites, 104 tests, 0 failures, 0 errors, 0 skipped.

Fixes risa-labs-inc#1218
Mihir-Rabari added a commit to Mihir-Rabari/BossConsole that referenced this pull request Sep 23, 2026
…d service bridges

NotificationServiceBridge, ContextMenuServiceBridge, DirectoryPickerServiceBridge,
PanelEventServiceBridge and PerformanceServiceBridge accepted any authenticated
process with no per-call attribution, while their siblings already enforce the
documented verified-caller-identity pattern (BossConsole#53, introduced for the
Secret Service in PR risa-labs-inc#505). A spawned process could spoof toasts, inject context-menu
registrations, drive the native directory dialog, rearrange panels and forge
performance mutations with nothing in the log to attribute them to.

- Add the sibling AUTHENTICATED_PROCESS_ID helper (mirror + KDoc citing risa-labs-inc#53/risa-labs-inc#505) to
  all five bridges; unary RPCs refuse with PERMISSION_DENIED when no verified identity
  is present, even with no interceptor in front of them.
- Performance watchers capture CURRENT_IDENTITY at subscription and revalidate the
  caller on every emission, the same mid-stream revocation shape LogServiceBridge uses.
- ContextMenuServiceBridge keeps its passive acknowledge-and-drop contract (issue risa-labs-inc#30)
  for verified callers; only unattributed callers change behaviour.
- New UnattributedBridgesIdentityTest: all five services over a real gRPC server with
  the production interceptor (no credential -> UNAUTHENTICATED, providers never
  reached), authenticated calls reach each provider, and direct calls with no identity
  context fail closed with PERMISSION_DENIED even without the interceptor.
- ContextMenuServiceBridgeTest now pins the refusal half of the risa-labs-inc#53 contract directly.

Gates: ktlintCheck + detekt + :composeApp:desktopTest --tests "ai.rever.boss.kernel.services.*"
in one invocation - 13 suites, 104 tests, 0 failures, 0 errors, 0 skipped.

Fixes risa-labs-inc#1218
fluck-boss pushed a commit that referenced this pull request Sep 23, 2026
…d service bridges (#1339)

NotificationServiceBridge, ContextMenuServiceBridge, DirectoryPickerServiceBridge,
PanelEventServiceBridge and PerformanceServiceBridge accepted any authenticated
process with no per-call attribution, while their siblings already enforce the
documented verified-caller-identity pattern (BossConsole#53, introduced for the
Secret Service in PR #505). A spawned process could spoof toasts, inject context-menu
registrations, drive the native directory dialog, rearrange panels and forge
performance mutations with nothing in the log to attribute them to.

- Add the sibling AUTHENTICATED_PROCESS_ID helper (mirror + KDoc citing #53/#505) to
  all five bridges; unary RPCs refuse with PERMISSION_DENIED when no verified identity
  is present, even with no interceptor in front of them.
- Performance watchers capture CURRENT_IDENTITY at subscription and revalidate the
  caller on every emission, the same mid-stream revocation shape LogServiceBridge uses.
- ContextMenuServiceBridge keeps its passive acknowledge-and-drop contract (issue #30)
  for verified callers; only unattributed callers change behaviour.
- New UnattributedBridgesIdentityTest: all five services over a real gRPC server with
  the production interceptor (no credential -> UNAUTHENTICATED, providers never
  reached), authenticated calls reach each provider, and direct calls with no identity
  context fail closed with PERMISSION_DENIED even without the interceptor.
- ContextMenuServiceBridgeTest now pins the refusal half of the #53 contract directly.

Gates: ktlintCheck + detekt + :composeApp:desktopTest --tests "ai.rever.boss.kernel.services.*"
in one invocation - 13 suites, 104 tests, 0 failures, 0 errors, 0 skipped.

Fixes #1218
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.

2 participants