Commit dc90553
feat: multi-tenant auth with per-user workspace isolation (nearai#1118)
* feat: multi-tenant auth with per-user scoping
Multi-user authentication and authorization for IronClaw gateway:
- Token-based auth mapping tokens to user IDs via GATEWAY_USER_TOKENS
- Per-user SSE broadcast scoping
- Per-user rate limiting with poisoned lock recovery
- Handler auth and ownership checks for jobs, settings, routines
- Extension secrets scoped per-user
- Chat handlers use authenticated identity
- Reverse proxy deployment documentation
- Comprehensive integration tests for auth, SSE, rate limiting, and job isolation
* fix: scope memory tools per-user in multi-tenant mode
Memory tools (search, write, read, tree) held a single workspace
created at startup with GATEWAY_USER_ID. In multi-tenant mode, all
users' tool calls searched the default user's scope.
Add WorkspaceResolver trait that resolves workspaces per-request using
JobContext.user_id. In single-user mode, returns the startup workspace.
In multi-tenant mode (GATEWAY_USER_TOKENS configured), creates and
caches per-user workspaces on demand.
Includes regression tests for workspace resolution and user isolation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: comprehensive multi-tenant isolation audit
Address all review findings from @serrrfirat plus 7 additional gaps
found via full security audit:
Reviewer findings (5):
- WorkspacePool now applies search config, memory layers, embedding
cache, identity read scopes, and global config scopes (was bare)
- jobs_summary_handler uses per-user queries instead of global counters
- jobs_prompt_handler restructured to not 404 agent jobs + ownership check
- jobs_restart_handler agent branch now verifies user ownership
- agent_job_summary_for_user added to Database trait + both backends
Audit findings (7):
- Delete dead handlers/memory.rs (stale copies with no auth)
- Add AuthenticatedUser to logs_events, logs_level_get, logs_level_set
- Add AuthenticatedUser to extensions_tools_handler, gateway_status_handler
- Add auth + ownership checks to all 6 routines handlers
- Add auth to all 4 skills handlers with audit logging on mutations
- Scope extension setup SSE broadcast to user (broadcast_for_user)
- Fix pre-existing test compilation errors in extensions/manager.rs
17 new multi-tenant isolation tests covering:
- WorkspacePool config propagation and scope merging
- Jobs handler per-user isolation (summary, restart, prompt, cancel)
- Routines handler auth enforcement and cross-user rejection
- Auth middleware enforcement on logs, skills, status endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: second-pass multi-tenant audit — scope SSE broadcasts, DB queries, dead handlers
Second audit pass applying learned patterns across the codebase:
- OAuth callback SSE broadcasts now use broadcast_for_user (lines 773, 912)
- jobs_list_handler uses list_agent_jobs_for_user instead of fetching
all users' jobs and filtering in Rust
- list_agent_jobs_for_user added to Database trait + postgres + libsql
- Dead handler files (extensions.rs, static_files.rs) hardened with
AuthenticatedUser to prevent auth regression if migrated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review findings — token hashing, broadcast scoping, error handling
Security fixes:
- Hash tokens with SHA-256 at construction time so authentication
compares fixed-size 32-byte digests, eliminating length-oracle
timing leaks
- Scope auth SSE broadcasts per-user in chat_auth_token_handler —
AuthRequired/AuthCompleted events were leaking across tenants
- Propagate DB errors in restart handlers instead of silently
swallowing via `if let Ok(Some(...))` pattern
Code quality:
- Log SSE serialization failures instead of silently producing empty
strings via unwrap_or_default()
- Remove dead `pub type AuthState = MultiAuthState` alias
- Replace `.unwrap()` with `Arc::clone(db)` in app.rs multi-tenant
workspace setup (db is guaranteed Some in context, but unwrap
violates project convention)
- Fix telegram setup test to inject UserIdentity into request
extensions (handler now requires AuthenticatedUser)
- Add safety comments on test-only expect/unwrap calls for CI
- Apply cargo fmt to fix pre-existing formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review findings — unify workspace pool, fix SSE regression, cache job owners
- Unify WorkspacePool and PerUserWorkspaceResolver: WorkspacePool now
implements WorkspaceResolver, eliminating duplicate per-user workspace
construction logic. app.rs uses WorkspacePool directly.
- Fix sse_tx: None scheduler regression: change scheduler/worker SSE
broadcasting from broadcast::Sender<SseEvent> to Arc<SseManager>,
restoring SSE event delivery for scheduled agent jobs.
- Cache job owner in orchestrator: add job_owner_cache to
OrchestratorState so job_event_handler avoids a DB round-trip on
every event after the first per job.
- Deduplicate ext_user_id computation in main.rs.
- Remove unused _gateway_state variable.
- Fix pre-existing test: first_token() returns None in multi-user mode
by design; align test assertion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix formatting in app.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract memory handlers back into handlers/memory.rs
Move memory API handlers out of server.rs into their own module,
consistent with how jobs, routines, and skills handlers are organized.
The resolve_workspace() helper moves with them since it is only used
by memory handlers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com>1 parent 070e8c7 commit dc90553
File tree
46 files changed
+5072
-1202
lines changed- src
- agent
- channels/web
- handlers
- tests
- cli
- config
- db
- libsql
- extensions
- history
- orchestrator
- tools
- builtin
- worker
- tests
- support
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
46 files changed
+5072
-1202
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
157 | 157 | | |
158 | 158 | | |
159 | 159 | | |
160 | | - | |
161 | | - | |
| 160 | + | |
| 161 | + | |
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| |||
235 | 235 | | |
236 | 236 | | |
237 | 237 | | |
238 | | - | |
239 | | - | |
| 238 | + | |
| 239 | + | |
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
| 71 | + | |
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
| 165 | + | |
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
171 | 171 | | |
172 | 172 | | |
173 | | - | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
174 | 176 | | |
175 | 177 | | |
176 | 178 | | |
| |||
227 | 229 | | |
228 | 230 | | |
229 | 231 | | |
230 | | - | |
| 232 | + | |
231 | 233 | | |
232 | 234 | | |
233 | 235 | | |
| |||
237 | 239 | | |
238 | 240 | | |
239 | 241 | | |
| 242 | + | |
240 | 243 | | |
241 | 244 | | |
242 | 245 | | |
| |||
259 | 262 | | |
260 | 263 | | |
261 | 264 | | |
262 | | - | |
| 265 | + | |
263 | 266 | | |
264 | 267 | | |
265 | 268 | | |
| |||
270 | 273 | | |
271 | 274 | | |
272 | 275 | | |
| 276 | + | |
273 | 277 | | |
274 | 278 | | |
275 | 279 | | |
| |||
289 | 293 | | |
290 | 294 | | |
291 | 295 | | |
292 | | - | |
| 296 | + | |
293 | 297 | | |
294 | 298 | | |
295 | 299 | | |
| |||
299 | 303 | | |
300 | 304 | | |
301 | 305 | | |
| 306 | + | |
302 | 307 | | |
303 | 308 | | |
304 | 309 | | |
| |||
324 | 329 | | |
325 | 330 | | |
326 | 331 | | |
327 | | - | |
| 332 | + | |
328 | 333 | | |
329 | 334 | | |
330 | 335 | | |
| |||
334 | 339 | | |
335 | 340 | | |
336 | 341 | | |
| 342 | + | |
337 | 343 | | |
338 | 344 | | |
339 | 345 | | |
| |||
346 | 352 | | |
347 | 353 | | |
348 | 354 | | |
| 355 | + | |
349 | 356 | | |
350 | 357 | | |
351 | 358 | | |
| |||
402 | 409 | | |
403 | 410 | | |
404 | 411 | | |
405 | | - | |
| 412 | + | |
406 | 413 | | |
407 | 414 | | |
408 | 415 | | |
| |||
417 | 424 | | |
418 | 425 | | |
419 | 426 | | |
| 427 | + | |
420 | 428 | | |
421 | 429 | | |
422 | 430 | | |
| |||
450 | 458 | | |
451 | 459 | | |
452 | 460 | | |
453 | | - | |
| 461 | + | |
454 | 462 | | |
455 | 463 | | |
456 | 464 | | |
| |||
465 | 473 | | |
466 | 474 | | |
467 | 475 | | |
| 476 | + | |
468 | 477 | | |
469 | 478 | | |
470 | 479 | | |
| |||
498 | 507 | | |
499 | 508 | | |
500 | 509 | | |
501 | | - | |
| 510 | + | |
502 | 511 | | |
503 | 512 | | |
504 | 513 | | |
505 | 514 | | |
506 | 515 | | |
| 516 | + | |
507 | 517 | | |
508 | 518 | | |
509 | 519 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | 12 | | |
14 | 13 | | |
15 | 14 | | |
| |||
67 | 66 | | |
68 | 67 | | |
69 | 68 | | |
70 | | - | |
71 | | - | |
| 69 | + | |
| 70 | + | |
72 | 71 | | |
73 | 72 | | |
74 | 73 | | |
| |||
102 | 101 | | |
103 | 102 | | |
104 | 103 | | |
105 | | - | |
106 | | - | |
107 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
108 | 107 | | |
109 | 108 | | |
110 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1646 | 1646 | | |
1647 | 1647 | | |
1648 | 1648 | | |
1649 | | - | |
| 1649 | + | |
1650 | 1650 | | |
1651 | 1651 | | |
1652 | 1652 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
327 | 327 | | |
328 | 328 | | |
329 | 329 | | |
330 | | - | |
| 330 | + | |
331 | 331 | | |
332 | 332 | | |
333 | 333 | | |
| |||
341 | 341 | | |
342 | 342 | | |
343 | 343 | | |
344 | | - | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
345 | 373 | | |
346 | 374 | | |
347 | 375 | | |
| |||
0 commit comments