Skip to content

fix: gate perspectives admin-overview on launcher credential, not ALL_CAPABILITY#675

Merged
lucksus merged 1 commit intodevfrom
perspective-capability-main-agent-fix
Feb 17, 2026
Merged

fix: gate perspectives admin-overview on launcher credential, not ALL_CAPABILITY#675
lucksus merged 1 commit intodevfrom
perspective-capability-main-agent-fix

Conversation

@lucksus
Copy link
Copy Markdown
Member

@lucksus lucksus commented Feb 17, 2026

The perspectives() resolver previously identified "admin" requests by checking whether the token held ALL_CAPABILITY wildcards and had no user email. This meant any regular app token granted ALL_CAPABILITY through the normal auth flow would also receive a full view of every perspective on the node — including ones owned by other users — without being able to do anything meaningful in them.

Replace that heuristic with an explicit is_admin_credential flag on RequestContext, set to true only when the incoming token is the launcher's admin_credential (or an empty token in legacy single-user mode). JWT app tokens never have this flag, regardless of which capabilities they hold. Agents that are not the launcher now only see perspectives they own or have explicitly joined via a neighbourhood.

Summary by CodeRabbit

  • New Features
    • Added admin credential token authentication support for identifying and verifying admin users
    • Implemented explicit admin status tracking and propagation across authentication and request handling flows
    • Refined admin resource authorization to use credential-based verification instead of implicit detection

…_CAPABILITY

The perspectives() resolver previously identified "admin" requests by checking
whether the token held ALL_CAPABILITY wildcards and had no user email. This
meant any regular app token granted ALL_CAPABILITY through the normal auth flow
would also receive a full view of every perspective on the node — including
ones owned by other users — without being able to do anything meaningful in
them.

Replace that heuristic with an explicit is_admin_credential flag on
RequestContext, set to true only when the incoming token is the launcher's
admin_credential (or an empty token in legacy single-user mode). JWT app
tokens never have this flag, regardless of which capabilities they hold.
Agents that are not the launcher now only see perspectives they own or have
explicitly joined via a neighbourhood.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

This PR introduces admin credential token verification across the executor. It adds a new is_admin_credential_token() function for constant-time token comparison, augments RequestContext with an is_admin_credential flag, integrates admin checks into GraphQL handlers, and replaces capability-based admin detection with explicit credential verification.

Changes

Cohort / File(s) Summary
Admin Credential Verification
rust-executor/src/agent/capabilities/mod.rs
Added is_admin_credential_token() function to perform constant-time token comparison against optional admin credential; supports legacy mode with empty tokens.
RequestContext Extension
rust-executor/src/graphql/graphql_types.rs
Added is_admin_credential boolean field to RequestContext to indicate authentication via launcher's admin credential token.
GraphQL Handler Integration
rust-executor/src/graphql/mod.rs
Imported is_admin_credential_token function and integrated admin credential verification across HTTP and WebSocket request paths; computes and includes is_admin_credential flag in all RequestContext instantiations.
Admin Detection Logic Update
rust-executor/src/graphql/query_resolvers.rs
Replaced wildcard-based admin detection with explicit context.is_admin_credential flag check; admin path continues to grant full perspective overview while non-admins are filtered by ownership.

Sequence Diagram

sequenceDiagram
    participant Client
    participant GraphQLHandler as GraphQL Handler
    participant AdminCheck as Admin Credential Check
    participant RequestCtx as RequestContext
    participant Resolver as Query Resolver
    
    Client->>GraphQLHandler: HTTP Request + auth_header
    GraphQLHandler->>AdminCheck: is_admin_credential_token(auth_header, admin_credential)
    AdminCheck-->>GraphQLHandler: bool (true/false)
    GraphQLHandler->>RequestCtx: Create RequestContext with is_admin_credential flag
    GraphQLHandler->>Resolver: Execute query with RequestContext
    alt is_admin_credential = true
        Resolver->>Resolver: Return all perspectives (full overview)
    else is_admin_credential = false
        Resolver->>Resolver: Filter perspectives by ownership
    end
    Resolver-->>Client: Response
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A token hops through the auth with grace,
Constant-time checks find its rightful place,
Admin credentials now verified clear,
In RequestContext, the flag brings cheer,
No more wild-card hunting here! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: replacing an implicit ALL_CAPABILITY heuristic with an explicit launcher credential check for admin-overview access to perspectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perspective-capability-main-agent-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust-executor/src/graphql/query_resolvers.rs (1)

440-464: ⚠️ Potential issue | 🟠 Major

The single-perspective resolver doesn't honor is_admin_credential.

The perspectives() list resolver (line 566) grants the launcher admin full visibility, but the perspective(uuid) resolver (line 440) still filters via can_access_perspective without checking is_admin_credential. This means an admin can discover a perspective UUID in the list but receive None when querying it directly.

Apply the same admin bypass to the single-perspective resolver:

Suggested fix
         if let Some(p) = get_perspective(&uuid) {
             let handle = p.persisted.lock().await.clone();
 
+            // Admin (launcher) can access any perspective
+            if context.is_admin_credential {
+                return Ok(Some(handle));
+            }
+
             // Check if user has access to this perspective
             let user_email = user_email_from_token(context.auth_token.clone());
 
             if can_access_perspective(&user_email, &handle) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-executor/src/graphql/query_resolvers.rs` around lines 440 - 464, The
perspective resolver currently always enforces can_access_perspective, causing
admin users to be blocked; update the async fn perspective to extract the
user_email via user_email_from_token(context.auth_token.clone()) and check
is_admin_credential(&user_email) before calling can_access_perspective (i.e., if
is_admin_credential returns true, return Ok(Some(handle)) without further
checks). Ensure you keep the existing capability check (check_capability) and
the get_perspective/handle logic but short-circuit permission evaluation for
admins so admin users see the perspective the same way perspectives() does.
🧹 Nitpick comments (1)
rust-executor/src/agent/capabilities/mod.rs (1)

37-45: Consider deduplicating with capabilities_from_token and adding unit tests.

The admin-credential matching logic here (lines 40-44) is identical to the first block of capabilities_from_token (lines 264-276). You could have capabilities_from_token call is_admin_credential_token internally to keep the admin-check logic in one place:

♻️ Suggested deduplication in capabilities_from_token
 pub fn capabilities_from_token(
     token: String,
     admin_credential: Option<String>,
 ) -> Result<Vec<Capability>, String> {
-    match admin_credential {
-        Some(admin_credential) => {
-            // Use constant-time comparison to prevent timing attacks
-            if constant_time_eq(&token, &admin_credential) {
-                return Ok(vec![ALL_CAPABILITY.clone()]);
-            }
-        }
-        None => {
-            if token.is_empty() {
-                return Ok(vec![ALL_CAPABILITY.clone()]);
-            }
-        }
+    if is_admin_credential_token(&token, &admin_credential) {
+        return Ok(vec![ALL_CAPABILITY.clone()]);
     }

Also, since this is a security-critical function, consider adding unit tests:

🧪 Suggested tests
#[test]
fn admin_credential_token_matches() {
    let cred = Some("secret123".to_string());
    assert!(is_admin_credential_token("secret123", &cred));
    assert!(!is_admin_credential_token("wrong", &cred));
    assert!(!is_admin_credential_token("", &cred));
}

#[test]
fn admin_credential_none_legacy_mode() {
    assert!(is_admin_credential_token("", &None));
    assert!(!is_admin_credential_token("some-token", &None));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-executor/src/agent/capabilities/mod.rs` around lines 37 - 45, The
admin-check logic in is_admin_credential_token is duplicated in
capabilities_from_token; refactor capabilities_from_token to call
is_admin_credential_token(token, admin_credential) for the admin branch to
centralize matching logic, remove the duplicate block, and keep behavior
identical (constant-time compare when Some, empty token allowed when None).
After refactoring, add unit tests for is_admin_credential_token (e.g., matching,
non-matching, empty token with Some, and legacy None behavior) to cover security
cases and prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@rust-executor/src/graphql/query_resolvers.rs`:
- Around line 440-464: The perspective resolver currently always enforces
can_access_perspective, causing admin users to be blocked; update the async fn
perspective to extract the user_email via
user_email_from_token(context.auth_token.clone()) and check
is_admin_credential(&user_email) before calling can_access_perspective (i.e., if
is_admin_credential returns true, return Ok(Some(handle)) without further
checks). Ensure you keep the existing capability check (check_capability) and
the get_perspective/handle logic but short-circuit permission evaluation for
admins so admin users see the perspective the same way perspectives() does.

---

Nitpick comments:
In `@rust-executor/src/agent/capabilities/mod.rs`:
- Around line 37-45: The admin-check logic in is_admin_credential_token is
duplicated in capabilities_from_token; refactor capabilities_from_token to call
is_admin_credential_token(token, admin_credential) for the admin branch to
centralize matching logic, remove the duplicate block, and keep behavior
identical (constant-time compare when Some, empty token allowed when None).
After refactoring, add unit tests for is_admin_credential_token (e.g., matching,
non-matching, empty token with Some, and legacy None behavior) to cover security
cases and prevent regressions.

@lucksus lucksus merged commit eaf91cb into dev Feb 17, 2026
5 checks passed
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.

1 participant