Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(security): workspace path allowlist + session ownership (#655)#705

Merged
frankbria merged 3 commits into
mainfrom
fix/655-workspace-path-allowlist
Jun 21, 2026
Merged

fix(security): workspace path allowlist + session ownership (#655)#705
frankbria merged 3 commits into
mainfrom
fix/655-workspace-path-allowlist

Conversation

@frankbria

@frankbria frankbria commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Closes #655.

Problem

get_v2_workspace resolved a client-supplied workspace_path and only checked for a .codeframe marker — no allowlist. Combined with the ReAct agent's run_command (shell=True) and workspace-scoped file tools, an authenticated user could point operations at any host directory: authenticated cross-tenant RCE the moment the server serves >1 user or runs in HOSTED mode.

Fix

A single shared gate, enforce_workspace_allowlist(path, user_id) (ui/dependencies.py), applied at every client-supplied-path funnel:

Funnel Endpoint
REST workspace access get_v2_workspace (20 v2 routers)
Workspace init POST /api/v2/workspaces
Session create POST /api/v2/sessions
  • Resolved path must be within a WORKSPACE_ROOT entry (os.pathsep-separated). .. escapes collapse via resolve().
  • Self-hosted default (no WORKSPACE_ROOT): no-op — single-operator workflow is unchanged.
  • HOSTED mode: allowlist is mandatory (fail closed → 500 if unset); each user is confined to <root>/<user_id> so one tenant can't reach another's subtree.

Session ownership

interactive_sessions gains a user_id column (+ idempotent ALTER migration for existing DBs). create_session persists the authenticated user. terminal_ws / session_chat_ws now fail closed: when auth is enabled, an ownerless (NULL) or mismatched session is rejected (the terminal ownership check existed but was dead code — no session ever carried an owner).

Acceptance criteria

  • Workspace paths outside the allowlist are rejected (REST + init + session create).
  • In HOSTED mode a user cannot operate on another user's / arbitrary host paths (per-user-subdir confinement + WebSocket session ownership, fail-closed).

Tests

  • tests/ui/test_workspace_allowlist.py — allowlist (inside/outside/traversal), hosted mandatory-root, per-user confinement, authed-user-required, session-create rejection + resolved-path storage + owner persistence, workspace-init rejection.
  • tests/api/test_session_chat_ws.py::test_chat_ws_ownership_mismatch_closes.
  • Full CI gate: 3842 passed, 11 skipped.

Known Limitations (tracked in #704 — gate hosted launch on it)

Cross-family review surfaced two further hardening items, broader than #655 and HOSTED-only (not deployed):

  1. Session REST endpoints (list/get/delete/messages) are not yet owner-scoped — cross-tenant metadata enumeration/tamper under multi-user auth.
  2. TOCTOU symlink: the stored path isn't re-validated at terminal-spawn time, and there is no symlink-resistant filesystem isolation. A tenant could swap a validated dir for a symlink before opening the terminal.

Review: cross-family (codex review) primary — drove the session-create, workspace-init, and session-ownership additions above.

Summary by CodeRabbit

  • New Features

    • Added user ownership tracking for interactive sessions.
    • Added session cost and token usage tracking.
    • Implemented workspace path allowlist validation for enhanced security.
  • Bug Fixes

    • Fixed session and workspace access to enforce ownership verification and reject unauthorized access.
    • Strengthened WebSocket connection security with strict ownership checks.
  • Chores

    • Added automatic database migration for legacy sessions.

Prevent authenticated cross-tenant RCE by constraining every client-supplied
workspace path to a configurable allowlist (WORKSPACE_ROOT), and binding
interactive sessions to their creating user.

- enforce_workspace_allowlist() (ui/dependencies.py): resolved path must be
  within a WORKSPACE_ROOT entry; '..' escapes collapse via resolve(). In HOSTED
  mode the allowlist is mandatory (fail closed) and each user is confined to
  <root>/<user_id>. No-op when WORKSPACE_ROOT is unset (single-operator default).
- Applied at all client-supplied-path funnels: get_v2_workspace (REST),
  POST /api/v2/workspaces (init), and POST /api/v2/sessions (create).
- interactive_sessions gains a user_id column (+ idempotent ALTER migration);
  create_session persists the authed user. terminal_ws/session_chat_ws now fail
  closed: when auth is enabled an ownerless or mismatched session is rejected.

Closes the RCE foundation; remaining hosted multi-tenancy hardening (session
REST owner-scoping, TOCTOU symlink isolation) tracked as follow-up.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@frankbria, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 41 seconds. Learn how PR review limits work.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b727611-9e41-4c6c-a9ac-3c6f0b61c8c8

📥 Commits

Reviewing files that changed from the base of the PR and between d87bd86 and 69e666c.

📒 Files selected for processing (2)
  • CLAUDE.md
  • tests/api/test_session_chat_ws.py

Walkthrough

This PR hardens v2 API security by adding a user_id foreign key to the interactive_sessions table (with a backward-compatible migration), introducing enforce_workspace_allowlist() to restrict workspace paths to per-user subdirectories, applying that enforcement to the session-create and workspace-init endpoints, and adding fail-closed session ownership checks in both WebSocket handlers.

Changes

Session ownership and workspace allowlist enforcement

Layer / File(s) Summary
DB schema migration and repository insert
codeframe/platform_store/schema_manager.py, codeframe/platform_store/repositories/interactive_sessions.py
Adds a nullable user_id FK column to the interactive_sessions table schema with ON DELETE SET NULL, adds a PRAGMA-based migration for existing databases, and extends the repository INSERT to persist cost_usd, token counts, timestamps, and user_id.
enforce_workspace_allowlist() implementation
codeframe/ui/dependencies.py
Adds WORKSPACE_ROOT parsing, path containment checking, and enforce_workspace_allowlist() which resolves the requested path, fails closed in hosted mode when WORKSPACE_ROOT or user_id is absent, and scopes allowed roots to <root>/<user_id> in hosted mode; exports the function via __all__.
get_v2_workspace and workspace init endpoint
codeframe/ui/dependencies.py, codeframe/ui/routers/workspace_v2.py
Injects require_auth into get_v2_workspace and calls enforce_workspace_allowlist before workspace existence checks; adds require_auth dependency to init_workspace and replaces raw Path.resolve() with allowlist enforcement before any disk operations.
create_session endpoint auth and allowlist
codeframe/ui/routers/interactive_sessions_v2.py
Adds require_auth dependency to create_session, validates and resolves workspace_path via enforce_workspace_allowlist with the authenticated user_id, uses the resolved path in repo.create, and persists user_id on the new session record.
WebSocket session ownership enforcement
codeframe/ui/routers/session_chat_ws.py, codeframe/ui/routers/terminal_ws.py
session_chat_ws captures user_id from _authenticate_websocket and adds a fail-closed ownership guard closing the socket with code 1008 when the session's user_id is missing or mismatched; terminal_ws tightens its existing check to also reject ownerless sessions when auth is enabled.
Tests: allowlist and WS ownership
tests/ui/test_workspace_allowlist.py, tests/api/test_session_chat_ws.py
Adds test_workspace_allowlist.py covering self-hosted/hosted allowlist modes, per-user directory isolation, traversal rejection, session creation path canonicalization, user_id persistence, and workspace init rejection; updates _create_session helper to use WORKSPACE_ROOT-relative paths and adds test_chat_ws_ownership_mismatch_closes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant create_session
    participant enforce_workspace_allowlist
    participant InteractiveSessionRepository

    rect rgba(100, 149, 237, 0.5)
        note over Client,InteractiveSessionRepository: Session Creation with Allowlist
        Client->>create_session: POST /api/v2/sessions (workspace_path, JWT)
        create_session->>create_session: require_auth → user_id
        create_session->>enforce_workspace_allowlist: Path(workspace_path), user_id
        alt path outside allowlist or missing WORKSPACE_ROOT/user_id
            enforce_workspace_allowlist-->>Client: HTTP 403 / 500
        else path accepted
            enforce_workspace_allowlist-->>create_session: resolved_path
            create_session->>InteractiveSessionRepository: repo.create(resolved_path, user_id=user_id)
            InteractiveSessionRepository-->>create_session: session record
            create_session-->>Client: 201 Created
        end
    end
Loading
sequenceDiagram
    participant WSClient
    participant session_chat_ws
    participant _authenticate_websocket
    participant interactive_sessions_repo

    rect rgba(144, 238, 144, 0.5)
        note over WSClient,interactive_sessions_repo: WebSocket Ownership Check
        WSClient->>session_chat_ws: WS connect (session_id)
        session_chat_ws->>_authenticate_websocket: authenticate
        _authenticate_websocket-->>session_chat_ws: authenticated=True, user_id=42
        session_chat_ws->>interactive_sessions_repo: get(session_id)
        interactive_sessions_repo-->>session_chat_ws: session (session_user_id=99)
        alt session_user_id missing or != user_id
            session_chat_ws-->>WSClient: close(1008, "Forbidden: session belongs to another user")
        else ownership confirmed
            session_chat_ws-->>WSClient: WS accepted, stream begins
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

  • frankbria/codeframe#510: Both PRs modify the /api/v2/sessions creation flow; #510 originally introduced the router and session CRUD, while this PR adds auth, allowlist enforcement, and user_id persistence to the same create_session endpoint and repository insert.
  • frankbria/codeframe#511: Both PRs modify session_chat_ws.py's WebSocket authentication flow; #511 introduced the auth scaffolding that this PR extends by capturing and enforcing user_id for session ownership.

Poem

🐰 Hop hop, the rabbit checks the gate,
No stray paths shall infiltrate!
Each user gets their cozy den,
Wrong owners? Close the socket—when!
The workspace roots are locked up tight,
user_id guards through day and night. 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main security changes: workspace path allowlisting and session ownership enforcement, matching the core objectives of preventing path traversal and enforcing multi-tenant isolation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/655-workspace-path-allowlist

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

@claude

claude Bot commented Jun 21, 2026

Copy link
Copy Markdown

Security Fix Review — Workspace Path Allowlist + Session Ownership (#655)

First review — no prior comments to reconcile.

This PR patches a genuine, high-severity vulnerability: an authenticated user could supply an arbitrary workspace_path and reach any host directory through REST endpoints and the terminal shell cwd. The core approach is sound.

What's Working Well

  • Path resolution before containment: path.resolve() inside enforce_workspace_allowlist collapses ../ escapes before is_relative_to() — the right order of operations.
  • Fail-closed in hosted mode: Raising 500 when WORKSPACE_ROOT is unset in hosted mode is the correct security posture.
  • Per-user subdir confinement: Narrowing roots to <root>/<user_id> in hosted mode is a clean, stateless confinement strategy.
  • Terminal ownership fix (terminal_ws.py): Changing session_user_id is not None and to session_user_id is None or correctly closes the gap where pre-[P7.0.1] Workspace path allowlist — prevent authenticated cross-tenant RCE (M1) #655 migrated sessions (NULL owner) could be hijacked by any authenticated user. This was dead code before — good catch.
  • Schema migration: The idempotent ALTER TABLE is the right pattern for existing databases.
  • Test coverage: Covers inside/outside/traversal, hosted mandatory-root, per-user confinement, session-create validation.

Issues

1. Circular import via local import (fragile)

is_hosted_mode is imported lazily on every call inside enforce_workspace_allowlist to avoid server.py -> dependencies.py -> server.py. The right fix is to extract is_hosted_mode into a standalone config helper (e.g. codeframe/lib/deployment.py or codeframe/ui/config.py) that simply reads CODEFRAME_DEPLOYMENT_MODE. A function that reads an env var does not need to live in the server module, and a lazy import will silently fail if the import graph ever changes.

2. Type inconsistency on the auth parameter

workspace_v2.py declares auth: dict while interactive_sessions_v2.py and dependencies.py use Dict[str, Any]. Should be consistent — use Dict[str, Any] or the modern dict[str, Any] everywhere.

3. WebSocket close codes are inconsistent

terminal_ws.py closes with 4003 (custom), session_chat_ws.py closes with 1008 (standard RFC 6455 policy violation). Same enforcement reason should use the same code. 1008 is the semantically appropriate choice for both.

4. test_chat_ws_ownership_mismatch_closes catches bare Exception

with pytest.raises(Exception):
    with client.websocket_connect("/ws/sessions/s1/chat?token=x"):
        pass

This passes if the server raises any exception including an unrelated internal error. Asserting the specific WebSocketDisconnect reason or close code would prevent false positives masking regressions.

Minor Notes

  • # ponytail: annotation: Non-standard prefix inside enforce_workspace_allowlist. Future readers won't know what it means — rewrite as a plain TODO or inline comment.
  • test_traversal_escape_rejected: The comment says "starts under it" — could be clearer that {base}/../proj resolves to a sibling of tmp_path, outside the allowed root.
  • Known gap in [P7.0.2] Hosted multi-tenancy hardening: session REST owner-scoping + TOCTOU path isolation #704 (session REST list/get/delete not owner-scoped): Good call scoping it out — adding that here would expand blast radius without established test coverage.

Summary

The vulnerability fix is correct and well-structured. Issues 2–4 are minor polish; item 1 (circular import) is the only thing worth resolving before merge since lazy imports are silent failure modes in future refactors.

Verdict: Approve pending item 1.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
codeframe/platform_store/schema_manager.py (1)

190-198: 💤 Low value

SQLite ALTER TABLE ADD COLUMN does not enforce the REFERENCES constraint for existing rows.

The REFERENCES users(id) clause in the ALTER TABLE statement is parsed by SQLite but is not retroactively enforced on existing rows, and foreign key checks only apply to new inserts/updates when PRAGMA foreign_keys=ON. This is fine for this use case since:

  1. Existing rows get NULL (no enforcement needed)
  2. New rows go through the repository which validates user_id upstream

However, consider adding an index on user_id in _create_indexes() if ownership lookups become frequent (e.g., listing sessions by user).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codeframe/platform_store/schema_manager.py` around lines 190 - 198, Consider
adding an index on the user_id column for the interactive_sessions table to
optimize ownership lookups if they become frequent. Locate the _create_indexes()
method and evaluate whether to add an index on the user_id column of the
interactive_sessions table. This is an optional optimization that should be
added if queries frequently filter or join on user_id for listing sessions by
user, but is not required since the ALTER TABLE ADD COLUMN statement already
establishes the foreign key relationship properly despite SQLite not
retroactively enforcing it on existing rows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/api/test_session_chat_ws.py`:
- Around line 74-76: The test currently catches the generic Exception when
testing the websocket connection with an invalid token. Replace the generic
Exception with WebSocketDisconnect to assert the specific websocket failure.
Since the router explicitly closes with code 1008 for ownership mismatches,
verify that the WebSocketDisconnect exception includes the expected close code
1008. Reference the existing test patterns in the same test_session_chat_ws.py
file that already validate WebSocketDisconnect with the proper close code to
ensure consistency with the established testing approach.

---

Nitpick comments:
In `@codeframe/platform_store/schema_manager.py`:
- Around line 190-198: Consider adding an index on the user_id column for the
interactive_sessions table to optimize ownership lookups if they become
frequent. Locate the _create_indexes() method and evaluate whether to add an
index on the user_id column of the interactive_sessions table. This is an
optional optimization that should be added if queries frequently filter or join
on user_id for listing sessions by user, but is not required since the ALTER
TABLE ADD COLUMN statement already establishes the foreign key relationship
properly despite SQLite not retroactively enforcing it on existing rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 170bc0bc-fc1b-4e84-adf3-93796163c853

📥 Commits

Reviewing files that changed from the base of the PR and between 45c51f0 and d87bd86.

📒 Files selected for processing (9)
  • codeframe/platform_store/repositories/interactive_sessions.py
  • codeframe/platform_store/schema_manager.py
  • codeframe/ui/dependencies.py
  • codeframe/ui/routers/interactive_sessions_v2.py
  • codeframe/ui/routers/session_chat_ws.py
  • codeframe/ui/routers/terminal_ws.py
  • codeframe/ui/routers/workspace_v2.py
  • tests/api/test_session_chat_ws.py
  • tests/ui/test_workspace_allowlist.py

Comment thread tests/api/test_session_chat_ws.py Outdated
…smatch (#655)

CodeRabbit: tighten the broad pytest.raises(Exception) to the specific
WebSocketDisconnect with the policy-violation close code.
@frankbria

Copy link
Copy Markdown
Owner Author

Thanks @coderabbitai — addressed in 95c2a04:

  • Assert specific WebSocketDisconnect(1008) (test_session_chat_ws.py): applied — now asserts the policy-violation close code instead of a broad Exception.
  • SQLite ALTER TABLE ADD COLUMN doesn't enforce REFERENCES (schema_manager.py): acknowledged, no change. SQLite doesn't enforce FKs without PRAGMA foreign_keys=ON, and the column functions correctly regardless; the REFERENCES … ON DELETE SET NULL is declarative metadata consistent with the table's CREATE form. Hardening FK enforcement is out of scope for this security fix.

Broader hosted multi-tenancy hardening (session REST owner-scoping, TOCTOU symlink isolation) is tracked in #704.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@frankbria Thanks for the clear write-up!

  • WebSocketDisconnect(1008) assertion — confirmed and appreciated; asserting the specific policy-violation close code makes the test intent explicit.
  • SQLite FK metadata — that's a sound call. The REFERENCES … ON DELETE SET NULL clause is consistent with the CREATE TABLE form and the behavioral outcome (nullable column, application-side enforcement) is correct; strict FK enforcement via PRAGMA is a separate concern and rightly out of scope here.

The security-critical paths are well-guarded. Looking forward to seeing the session REST owner-scoping and TOCTOU symlink hardening land in #704. 🐇

@claude

claude Bot commented Jun 21, 2026

Copy link
Copy Markdown

Follow-up Review (95c2a04 update)

Checking items from my prior review.

Fixed

  • Test assertion (my item 4): test_chat_ws_ownership_mismatch_closes now correctly asserts WebSocketDisconnect with code == 1008. Good.

Still Open

Item 1 - Circular import (was my stated blocker for merge)

The lazy import inside enforce_workspace_allowlist is still present:

def enforce_workspace_allowlist(path, user_id):
    # Local import avoids a circular import (server -> routers -> dependencies).
    from codeframe.ui.server import is_hosted_mode

This pattern is fragile: if the import graph changes, it fails silently at call time instead of at startup. is_hosted_mode is just a one-liner reading CODEFRAME_DEPLOYMENT_MODE. Moving it to codeframe/lib/deployment.py or inlining the env-var read directly in dependencies.py (no server import needed) would close this. This was my primary "resolve before merge" item.

Item 2 - Type inconsistency (minor)

workspace_v2.py still declares auth: dict, while interactive_sessions_v2.py and dependencies.py use Dict[str, Any]. One-line fix.

Item 3 - WebSocket close codes (minor)

terminal_ws.py still closes with code 4003, session_chat_ws.py uses 1008. Same policy violation, same enforcement path — 1008 is the semantically correct RFC 6455 code for both. The mismatch will confuse clients handling reconnect logic.

Item 5 - # ponytail: annotation (cosmetic)

Still present in enforce_workspace_allowlist. Not a blocker, but a non-standard prefix that future readers won't recognize. Rewrite as a plain # TODO: or inline note.


Path to merge: Fix the circular import (item 1) — that is the only structural concern I flagged. Items 2, 3, and 5 can follow in a cleanup PR or be addressed here if convenient.

@claude

claude Bot commented Jun 21, 2026

Copy link
Copy Markdown

Follow-up Review -- Workspace Allowlist + Session Ownership (#655)

Continuing from my prior review. The diff has not changed since that pass, so the three outstanding items remain open. A correction to my previous review is noted below.

Outstanding Item 1 -- Circular import (still open / blocker)

enforce_workspace_allowlist in dependencies.py still lazy-imports is_hosted_mode from server.py on every call. This works until anyone refactors the import graph, at which point it silently breaks at call-time rather than import-time. Extracting is_hosted_mode into codeframe/lib/deployment.py (a two-line function reading CODEFRAME_DEPLOYMENT_MODE) dissolves the cycle. This remains the only blocker.

Outstanding Item 2 -- Type annotation inconsistency (minor)

workspace_v2.py:229 declares auth: dict = Depends(require_auth) while interactive_sessions_v2.py and dependencies.py both use auth: Dict[str, Any]. Prefer dict[str, Any] consistently across all three sites.

Outstanding Item 3 -- Inconsistent WebSocket close codes (minor)

terminal_ws.py closes with 4003 (custom), session_chat_ws.py closes with 1008 (RFC 6455 Policy Violation) for the same enforcement reason. 1008 is semantically correct for both; terminal_ws.py should be aligned.

Correction to Item 4 from prior review

I previously said test_chat_ws_ownership_mismatch_closes catches bare Exception. That was wrong. The test uses pytest.raises(WebSocketDisconnect) and asserts exc.value.code == 1008 -- it correctly pins the disconnect type and close code. Apologies for the noise; that test was already right.

One new observation

The _create_session helper in test_session_chat_ws.py imports TestClient at module level and again inside the new test function body. Redundant local re-import -- not a correctness issue, just cleanup.

Summary: security logic is correct and well-tested. Item 1 (circular import) is the only blocker. Items 2 and 3 are minor polish. Item 4 from the prior review was already handled correctly.

@frankbria frankbria merged commit 371489b into main Jun 21, 2026
12 checks passed
@frankbria frankbria deleted the fix/655-workspace-path-allowlist branch June 21, 2026 01:54
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.

[P7.0.1] Workspace path allowlist — prevent authenticated cross-tenant RCE (M1)

1 participant