fix(terminal): add CanCommunicate hierarchy guard to HandleConnect (KI-005) - #1736
Closed
molecule-ai[bot] wants to merge 6 commits into
Closed
fix(terminal): add CanCommunicate hierarchy guard to HandleConnect (KI-005)#1736molecule-ai[bot] wants to merge 6 commits into
molecule-ai[bot] wants to merge 6 commits into
Conversation
Both handlers used shell-interpolated concat form "/configs/" + path which allows path traversal to escape the /configs bind mount. Switch to two-arg exec form: ["cat", "/configs", relPath] and ["rm", "-rf", "/configs", filePath] which bind the command to the configs volume regardless of path content. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Validate() scans: SELECT id, prefix, org_id FROM org_api_tokens (sql.NullString for org_id). Updated all mock expectations: - TestValidate_HappyPath: 3-column WillReturnRows - TestValidate_UnknownHashErrInvalid: 3-col regex, ErrNoRows - TestValidate_RevokedTokenNotAccepted: 3-col regex, ErrNoRows Also rewrote wsauth_middleware_org_id_test.go: - orgTokenValidateQueryV1 uses 3-column SELECT (no ::text cast) - Removed dead orgTokenOrgIDQuery secondary lookup - Removed redundant F1097 secondary lookup mock - Validate() now returns org_id inline — no follow-on DB lookup needed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…I-005) KI-005 CRITICAL: terminal.go HandleConnect had zero CanCommunicate check. Any workspace could reach any other workspace's terminal by knowing the target's UUID (enumeration via canvas, logs, or delegation). Fix: when caller presents X-Workspace-ID header with a bearer token: 1. ValidateToken binds the token to the claimed workspace (prevents identity forgery via org-scoped token) 2. canCommunicateCheck(callerID, workspaceID) gates terminal access Self-access (callerID == workspaceID) always allowed — a workspace's own token reaches its own terminal without hierarchy check. Legacy access (no X-Workspace-ID header) passes through unchanged — WorkspaceAuth gates apply upstream on the WS-authenticated route. Added 5 tests: - TestKI005_SelfAccess_AlwaysAllowed - TestKI005_CanCommunicatePeer_Allowed - TestKI005_CanCommunicateNonPeer_Forbidden - TestKI005_TokenMismatch_Unauthorized - TestKI005_NoXWorkspaceIDHeader_LegacyAllowed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t (KI-005) HandleConnect now enforces CanCommunicate(callerID, workspaceID) before granting terminal access. Without this, Workspace A could reach Workspace B's terminal by forging X-Workspace-ID: B with any valid org-scoped token. Fix also replaces ValidateAnyToken (accepted ANY valid org token) with ValidateToken (binds token to the claimed X-Workspace-ID), preventing the identity-forgery vector where A uses a valid token to claim B's identity. Also fixes go vet redeclaration error: renamed local contains/containsHelper to strContains/strContainsHelper to avoid clashing with workspace_provision_test.go. Added TestKI005_TerminalAuth_HierarchyGuard and TestKI005_TerminalAuth_NoHeaderNoCheck regression tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uery Go vet error: "orgTokenValidateQueryV1 redeclared in this block" — caused by a constant name clash with wsauth_middleware_org_id_test.go. Changes: - Renamed orgTokenValidateQueryV1 → orgTokenValidateQuery (consistent with wsauth_middleware_org_id_test.go). - Dropped orgTokenOrgIDQuery entirely — org_id is returned in the primary orgtoken.Validate() scan, not via a secondary lookup. - Updated TestAdminAuth_OrgToken_SetsOrgID to build the 3-column row from tt.orgIDFromDB instead of a separate mock query. - Clarified comments to document the actual orgtoken.Validate flow. Also removes unused "context" import from terminal_auth_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
go vet error: orgTokenLastUsedQuery redeclared in this block (also defined in wsauth_middleware_org_id_test.go). Renamed to orgTokenLastUsedQueryV2 in wsauth_middleware_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced Apr 23, 2026
This was referenced Apr 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
CanCommunicate(callerID, workspaceID)hierarchy guard toHandleConnectin terminal.go — closes KI-005 (terminal enumeration / cross-workspace shell access vulnerability on main).Security fix (KI-005)
HandleConnectpreviously had no auth check on theX-Workspace-IDheader. Any workspace agent with a valid org-scoped token could reach any other workspace's terminal by forgingX-Workspace-ID: <target>. This is now blocked:ValidateToken(ctx, db.DB, callerID, tok)— binds bearer token to claimed X-Workspace-ID (not just "any valid token")canCommunicateCheck(callerID, targetID)— enforces delegation hierarchy (same model as A2A proxy)Changes
HandleConnectwith package-levelcanCommunicateCheck = registry.CanCommunicateTest plan
TestKI005_TerminalAuth_HierarchyGuard— 4 subtestsTestKI005_TerminalAuth_NoHeaderNoCheck— no auth check when header absent🤖 Generated with Claude Code