Files
Agent-System/internal/workspace/cleanup.go
T
weichenandCursor 2ddaf769ca feat(workspace): gate cleanup on verified receipts
Hash deliverables through Capability.ReadArtifact, resolve backends on the server, and refuse workspace removal until frozen members match FAV receipts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 02:06:10 +00:00

112 lines
2.6 KiB
Go

package workspace
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
// OpaqueID turns a stored work dir or already-opaque id into a WorkspaceRef.ID.
// It never returns a physical path.
func OpaqueID(parts ...string) string {
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
slash := filepath.ToSlash(p)
if i := strings.LastIndex(slash, "/workspaces/"); i >= 0 {
rest := slash[i+len("/workspaces/"):]
if rest == "" {
continue
}
return strings.Split(rest, "/")[0]
}
if !strings.Contains(slash, "/") {
return p
}
return filepath.Base(filepath.Clean(p))
}
return ""
}
type RetentionReceipt struct {
LogicalID string
Digest string
StorageIdentity string
Verified bool
}
type FrozenManifest struct {
SHA256 string
StorageIdentity string
WorkspaceID string
Members []FrozenMember
}
type FrozenMember struct {
LogicalID string
Digest string
Kind string
}
func memberNeedsRetention(kind string) bool {
switch kind {
case "document", "assistant_turn", "run_manifest", "attachment":
return true
default:
return false
}
}
var ErrCleanupBlocked = fmt.Errorf("workspace: cleanup blocked until verified receipts match the frozen manifest")
func AssertCleanupAllowed(frozen FrozenManifest, receipts []RetentionReceipt) error {
if frozen.SHA256 == "" || frozen.StorageIdentity == "" {
return ErrCleanupBlocked
}
byLogical := map[string]RetentionReceipt{}
for _, r := range receipts {
byLogical[r.LogicalID] = r
}
for _, mem := range frozen.Members {
if !memberNeedsRetention(mem.Kind) {
continue
}
r, ok := byLogical[mem.LogicalID]
if !ok || !r.Verified {
return ErrCleanupBlocked
}
if r.Digest != mem.Digest || r.StorageIdentity != frozen.StorageIdentity {
return ErrCleanupBlocked
}
}
return nil
}
// Cleanup removes the workspace directory only after verified receipts match the frozen manifest.
func (c *Capability) Cleanup(ctx context.Context, ref WorkspaceRef, frozen FrozenManifest, receipts []RetentionReceipt) error {
if err := AssertCleanupAllowed(frozen, receipts); err != nil {
return err
}
if err := c.guard(ctx, ref, false); err != nil {
return err
}
root := filepath.Join(c.dataRoot, "workspaces", ref.ID)
abs, err := filepath.Abs(root)
if err != nil {
return err
}
dataRoot, err := filepath.Abs(c.dataRoot)
if err != nil {
return err
}
rel, err := filepath.Rel(dataRoot, abs)
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return ErrPathEscape
}
return os.RemoveAll(abs)
}