@@ -1232,7 +1232,7 @@ func TestHandleBindStreamFrameErrors(t *testing.T) {
12321232 Payload : protocol.BindStreamParams {
12331233 SessionID : "session-1" ,
12341234 },
1235- })
1235+ }, nil )
12361236 if response .Type != FrameTypeError {
12371237 t .Fatalf ("response type = %q, want %q" , response .Type , FrameTypeError )
12381238 }
@@ -1271,7 +1271,7 @@ func TestHandleBindStreamFrameErrors(t *testing.T) {
12711271 SessionID : "session-1" ,
12721272 Channel : "ipc" ,
12731273 },
1274- })
1274+ }, nil )
12751275 if response .Type != FrameTypeError {
12761276 t .Fatalf ("response type = %q, want %q" , response .Type , FrameTypeError )
12771277 }
@@ -1281,6 +1281,110 @@ func TestHandleBindStreamFrameErrors(t *testing.T) {
12811281 })
12821282}
12831283
1284+ func TestHandleBindStreamFrameRejectsSessionOutsideCurrentWorkspace (t * testing.T ) {
1285+ relay := NewStreamRelay (StreamRelayOptions {})
1286+ ctx , cancel := context .WithCancel (context .Background ())
1287+ defer cancel ()
1288+
1289+ connectionID := NewConnectionID ()
1290+ workspaceState := NewConnectionWorkspaceState ()
1291+ workspaceState .SetWorkspaceHash ("workspace-b" )
1292+ connectionCtx := WithConnectionID (ctx , connectionID )
1293+ connectionCtx = WithConnectionWorkspaceState (connectionCtx , workspaceState )
1294+ connectionCtx = WithStreamRelay (connectionCtx , relay )
1295+ if err := relay .RegisterConnection (ConnectionRegistration {
1296+ ConnectionID : connectionID ,
1297+ Channel : StreamChannelIPC ,
1298+ Context : connectionCtx ,
1299+ Cancel : cancel ,
1300+ Write : func (message RelayMessage ) error {
1301+ _ = message
1302+ return nil
1303+ },
1304+ Close : func () {},
1305+ }); err != nil {
1306+ t .Fatalf ("register connection: %v" , err )
1307+ }
1308+ defer relay .dropConnection (connectionID )
1309+
1310+ runtimeStub := & bootstrapRuntimeStub {
1311+ loadSessionFn : func (context.Context , LoadSessionInput ) (Session , error ) {
1312+ return Session {}, ErrRuntimeResourceNotFound
1313+ },
1314+ }
1315+ response := handleBindStreamFrame (connectionCtx , MessageFrame {
1316+ Type : FrameTypeRequest ,
1317+ Action : FrameActionBindStream ,
1318+ RequestID : "bind-cross-workspace" ,
1319+ Payload : protocol.BindStreamParams {
1320+ SessionID : "session-from-workspace-a" ,
1321+ Channel : "all" ,
1322+ },
1323+ }, runtimeStub )
1324+ if response .Type != FrameTypeError {
1325+ t .Fatalf ("response type = %q, want %q" , response .Type , FrameTypeError )
1326+ }
1327+ if response .Error == nil || response .Error .Code != ErrorCodeResourceNotFound .String () {
1328+ t .Fatalf ("response error = %#v, want resource_not_found" , response .Error )
1329+ }
1330+ if got := relay .ResolveFallbackSessionIDForWorkspace (connectionID , "workspace-b" ); got != "" {
1331+ t .Fatalf ("binding should not be written after validation failure, got fallback %q" , got )
1332+ }
1333+ }
1334+
1335+ func TestHandleBindStreamFrameValidatesVisibleSessionBeforeBinding (t * testing.T ) {
1336+ relay := NewStreamRelay (StreamRelayOptions {})
1337+ ctx , cancel := context .WithCancel (context .Background ())
1338+ defer cancel ()
1339+
1340+ connectionID := NewConnectionID ()
1341+ workspaceState := NewConnectionWorkspaceState ()
1342+ workspaceState .SetWorkspaceHash ("workspace-a" )
1343+ connectionCtx := WithConnectionID (ctx , connectionID )
1344+ connectionCtx = WithConnectionWorkspaceState (connectionCtx , workspaceState )
1345+ connectionCtx = WithStreamRelay (connectionCtx , relay )
1346+ if err := relay .RegisterConnection (ConnectionRegistration {
1347+ ConnectionID : connectionID ,
1348+ Channel : StreamChannelIPC ,
1349+ Context : connectionCtx ,
1350+ Cancel : cancel ,
1351+ Write : func (message RelayMessage ) error {
1352+ _ = message
1353+ return nil
1354+ },
1355+ Close : func () {},
1356+ }); err != nil {
1357+ t .Fatalf ("register connection: %v" , err )
1358+ }
1359+ defer relay .dropConnection (connectionID )
1360+
1361+ var loaded LoadSessionInput
1362+ runtimeStub := & bootstrapRuntimeStub {
1363+ loadSessionFn : func (_ context.Context , input LoadSessionInput ) (Session , error ) {
1364+ loaded = input
1365+ return Session {ID : input .SessionID }, nil
1366+ },
1367+ }
1368+ response := handleBindStreamFrame (connectionCtx , MessageFrame {
1369+ Type : FrameTypeRequest ,
1370+ Action : FrameActionBindStream ,
1371+ RequestID : "bind-visible-session" ,
1372+ Payload : protocol.BindStreamParams {
1373+ SessionID : "session-visible" ,
1374+ Channel : "all" ,
1375+ },
1376+ }, runtimeStub )
1377+ if response .Type != FrameTypeAck {
1378+ t .Fatalf ("response type = %q, want %q: %#v" , response .Type , FrameTypeAck , response .Error )
1379+ }
1380+ if loaded .SessionID != "session-visible" {
1381+ t .Fatalf ("validated session_id = %q, want %q" , loaded .SessionID , "session-visible" )
1382+ }
1383+ if got := relay .ResolveFallbackSessionIDForWorkspace (connectionID , "workspace-a" ); got != "session-visible" {
1384+ t .Fatalf ("fallback session = %q, want %q" , got , "session-visible" )
1385+ }
1386+ }
1387+
12841388func TestHandleTriggerActionFrame (t * testing.T ) {
12851389 registerConnection := func (
12861390 t * testing.T ,
0 commit comments