fix(ssh): don't leak session-scoped metadata across sibling sessions
reuseExistingEnvironment copies persistent metadata from the previous ExecutorRunning row into the new launch request so executors can pick up worktree/container/sandbox IDs. For SSH that swept in session-scoped keys too — agentctl PID/port, session dir, local forward port — which made the second session's ResumeRemoteInstance reattach to the first session's agentctl process. Mark those keys as session-scoped and skip them in applyExecutorRunningMetadata; the same-session resume path (applyRunningRecordToResumeRequest) still copies them because it works off the session's own ExecutorRunning record.
This commit is contained in:
@@ -152,6 +152,21 @@ var persistentMetadataPrefixes = []string{
|
|||||||
"env_secret_id_", // Secret store UUIDs for profile env vars
|
"env_secret_id_", // Secret store UUIDs for profile env vars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sessionScopedMetadataKeys lists metadata keys that point to per-session
|
||||||
|
// runtime resources (process PIDs, allocated ports, session directories on
|
||||||
|
// the remote). These keys ARE persisted across a SAME-session resume — that's
|
||||||
|
// how a backend restart reattaches to a still-running remote agent — but they
|
||||||
|
// MUST NOT be carried across SIBLING sessions on the same task. If they were,
|
||||||
|
// the second session would try to attach to the first session's agentctl
|
||||||
|
// process and end up sharing its ACP session and instance port.
|
||||||
|
var sessionScopedMetadataKeys = map[string]bool{
|
||||||
|
MetadataKeySSHRemoteSessionDir: true,
|
||||||
|
MetadataKeySSHRemoteAgentctlPort: true,
|
||||||
|
MetadataKeySSHRemoteAgentctlPID: true,
|
||||||
|
MetadataKeySSHLocalForwardPort: true,
|
||||||
|
MetadataKeySSHRemoteAgentctlURL: true,
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldPersistMetadataKey returns true if the given metadata key should
|
// ShouldPersistMetadataKey returns true if the given metadata key should
|
||||||
// be carried forward when resuming a session from an ExecutorRunning record.
|
// be carried forward when resuming a session from an ExecutorRunning record.
|
||||||
func ShouldPersistMetadataKey(key string) bool {
|
func ShouldPersistMetadataKey(key string) bool {
|
||||||
@@ -166,6 +181,13 @@ func ShouldPersistMetadataKey(key string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSessionScopedMetadataKey reports whether key references per-session
|
||||||
|
// runtime resources that must not leak across sibling sessions on the same
|
||||||
|
// task environment.
|
||||||
|
func IsSessionScopedMetadataKey(key string) bool {
|
||||||
|
return sessionScopedMetadataKeys[key]
|
||||||
|
}
|
||||||
|
|
||||||
// FilterPersistentMetadata returns a copy of src containing only keys that
|
// FilterPersistentMetadata returns a copy of src containing only keys that
|
||||||
// should be carried forward across session resumes. Returns nil if no keys match.
|
// should be carried forward across session resumes. Returns nil if no keys match.
|
||||||
func FilterPersistentMetadata(src map[string]interface{}) map[string]interface{} {
|
func FilterPersistentMetadata(src map[string]interface{}) map[string]interface{} {
|
||||||
|
|||||||
@@ -133,9 +133,21 @@ func applyExecutorRunningMetadata(req *LaunchAgentRequest, running *models.Execu
|
|||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
metadata = ensureLaunchMetadata(req)
|
metadata = ensureLaunchMetadata(req)
|
||||||
}
|
}
|
||||||
if _, exists := metadata[k]; !exists && lifecycle.ShouldPersistMetadataKey(k) {
|
if _, exists := metadata[k]; exists {
|
||||||
metadata[k] = v
|
continue
|
||||||
}
|
}
|
||||||
|
if !lifecycle.ShouldPersistMetadataKey(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Skip session-scoped runtime resources (PIDs, ports, session dirs).
|
||||||
|
// Carrying these across sibling sessions on the same task makes a
|
||||||
|
// fresh launch look like a same-session resume — see SSH executor's
|
||||||
|
// ResumeRemoteInstance — and the new session ends up sharing the
|
||||||
|
// previous session's agentctl process.
|
||||||
|
if lifecycle.IsSessionScopedMetadataKey(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metadata[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user