You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Prevent OAuth token leakage via static Authorization headers (HolmesGPT#2095)
## Summary
This PR adds security guards to prevent privilege escalation when OAuth
is configured but no user token is available. It ensures that static
Authorization headers from configuration cannot silently substitute for
missing per-user OAuth tokens, which could leak shared service-account
credentials.
## Key Changes
### 1. Server-Mode User ID Guard in OAuthTokenManager
- Added `_is_server_mode()` method to detect when running against the
DB-backed token store (multi-user server)
- Added guards in `get_access_token()`, `has_token()`, and
`store_token()` to refuse cache operations without a valid user_id in
server mode
- Prevents cache key collisions where one caller's token could be served
to another via the DEFAULT_CLI_USER fallback
- CLI mode (DiskTokenStore) continues to work normally with
DEFAULT_CLI_USER as the legitimate single-user identity
### 2. Static Authorization Header Stripping in RemoteMCPToolset
- Modified `_render_headers()` to strip any Authorization header
(case-insensitive) when OAuth is configured but no token is available
- Only strips headers when OAuth is enabled AND no cached token exists
- Preserves other headers and only strips Authorization to prevent
service-account credential substitution
- Logs appropriate warnings distinguishing between "no token" and "no
token + header stripped" cases
### 3. Comprehensive Test Coverage
- Added `TestServerModeUserIdGuard` class with 5 tests covering:
- Token retrieval/storage refusal without user_id in server mode
- Legitimate per-user token access still works
- CLI mode continues to allow DEFAULT_CLI_USER
- Added 3 tests for Authorization header stripping:
- Strips static Authorization when OAuth enabled but no token
- Case-insensitive matching (handles "Authorization", "authorization",
"AUTHORIZATION")
- Preserves static Authorization when OAuth is disabled
## Implementation Details
- The server-mode guard mirrors the existing guard in
`DalTokenStore.get_token()` to maintain consistency
- Authorization header stripping uses case-insensitive key matching to
handle all header case variations
- Logging distinguishes between scenarios to aid debugging (token
missing vs. token missing + header stripped)
- No changes to the OAuth flow itself—only prevents unsafe fallback
behavior
https://claude.ai/code/session_01AAECGd71rhfPJfqgDWHtmp
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented cross-user token use; token checks and stores now fail
safely when no user ID is present.
* Static Authorization headers are suppressed when per-user OAuth tokens
are required, with clearer warnings for likely 401s.
* **Chores**
* CLI and interactive flows now consistently supply a default user ID in
request contexts.
* **Tests**
* Expanded coverage for per-user token behavior, header suppression, and
CLI/interactive flows.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/HolmesGPT/holmesgpt/pull/2095?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments