Files
goclaw/internal/tools/delegation_artifacts_windows.go
Duc Nguyenandntduc bb7712a9ff fix(collaboration): harden delegated task isolation (#1486)
* feat(collaboration): isolate delegated artifacts and child runs

Isolate delegated inputs and outputs behind secure artifact exchange lifecycles. Scope Agent Link tasks by tenant and root agent, and enforce delegation spawn-tree boundaries. Add process-wide child-run admission and preserve logical media paths across native, MCP, and sandbox execution.

* fix(collaboration): harden delegated task isolation

Enforce tenant and root-agent task scope across migrations and stores. Add exactly-once async completion delivery, delegated sandbox boundaries, and confined artifact and media recovery across runtime surfaces.

* fix(collaboration): recover interrupted async tasks

* fix(collaboration): normalize persisted child-run status

---------

Co-authored-by: ntduc <ntduc@cpp.ai.vn>
2026-07-30 14:17:40 +07:00

575 lines
15 KiB
Go

//go:build windows
package tools
import (
"errors"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
type artifactSecureRoot struct {
handle windows.Handle
}
type artifactFileRenameInformation struct {
ReplaceIfExists uint32
RootDirectory windows.Handle
FileNameLength uint32
FileName [1]uint16
}
func openArtifactSecureRoot(hostPath string) (*artifactSecureRoot, error) {
absolute, err := filepath.Abs(hostPath)
if err != nil {
return nil, err
}
handle, err := artifactNtOpen(
0,
artifactNTAbsolutePath(absolute),
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
return nil, translateArtifactWindowsError(err)
}
if err := rejectArtifactReparsePoint(handle); err != nil {
windows.CloseHandle(handle)
return nil, err
}
return &artifactSecureRoot{handle: handle}, nil
}
func (r *artifactSecureRoot) close() error {
if r == nil || r.handle == 0 || r.handle == windows.InvalidHandle {
return nil
}
handle := r.handle
r.handle = windows.InvalidHandle
return windows.CloseHandle(handle)
}
func (r *artifactSecureRoot) mkdirAll(relativePath string, _ fs.FileMode) error {
components, err := artifactPathComponentsWindows(relativePath)
if err != nil {
return err
}
current, err := duplicateArtifactHandle(r.handle)
if err != nil {
return err
}
defer windows.CloseHandle(current)
for _, component := range components {
next, err := artifactNtOpen(
current,
component,
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_OPEN_IF,
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
windows.FILE_ATTRIBUTE_NORMAL,
)
if err != nil {
return translateArtifactWindowsError(err)
}
if err := rejectArtifactReparsePoint(next); err != nil {
windows.CloseHandle(next)
return err
}
windows.CloseHandle(current)
current = next
}
return nil
}
func (r *artifactSecureRoot) createSubroot(relativePath string, _ fs.FileMode) (*artifactSecureRoot, error) {
parent, name, err := r.openParent(relativePath)
if err != nil {
return nil, err
}
defer windows.CloseHandle(parent)
handle, err := artifactNtOpen(
parent,
name,
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_CREATE,
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
windows.FILE_ATTRIBUTE_NORMAL,
)
if err != nil {
return nil, translateArtifactWindowsError(err)
}
if err := rejectArtifactReparsePoint(handle); err != nil {
windows.CloseHandle(handle)
return nil, err
}
return &artifactSecureRoot{handle: handle}, nil
}
func (r *artifactSecureRoot) openSubroot(relativePath string) (*artifactSecureRoot, error) {
handle, err := r.openDir(relativePath)
if err != nil {
return nil, err
}
return &artifactSecureRoot{handle: handle}, nil
}
func (r *artifactSecureRoot) createFile(relativePath string, _ fs.FileMode) (*os.File, error) {
parent, name, err := r.openParent(relativePath)
if err != nil {
return nil, err
}
defer windows.CloseHandle(parent)
handle, err := artifactNtOpen(
parent,
name,
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_CREATE,
windows.FILE_NON_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
windows.FILE_ATTRIBUTE_NORMAL,
)
if err != nil {
return nil, translateArtifactWindowsError(err)
}
if err := rejectArtifactReparsePoint(handle); err != nil {
windows.CloseHandle(handle)
return nil, err
}
return os.NewFile(uintptr(handle), name), nil
}
func (r *artifactSecureRoot) openEntry(relativePath string) (*artifactSecureEntry, error) {
parent, name, err := r.openParent(relativePath)
if err != nil {
return nil, err
}
defer windows.CloseHandle(parent)
handle, err := artifactNtOpen(
parent,
name,
windows.FILE_GENERIC_READ|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
return nil, translateArtifactWindowsError(err)
}
var info windows.ByHandleFileInformation
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
windows.CloseHandle(handle)
return nil, err
}
if info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
windows.CloseHandle(handle)
return nil, errArtifactReparsePoint
}
kind := artifactEntryRegular
if info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
kind = artifactEntryDirectory
}
size := int64(uint64(info.FileSizeHigh)<<32 | uint64(info.FileSizeLow))
return &artifactSecureEntry{
file: os.NewFile(uintptr(handle), name),
kind: kind,
size: size,
links: uint64(info.NumberOfLinks),
}, nil
}
func (r *artifactSecureRoot) readDir(relativePath string) ([]string, error) {
entry, err := r.openEntry(relativePath)
if err != nil {
return nil, err
}
defer entry.close()
if entry.kind != artifactEntryDirectory {
return nil, ErrArtifactNonRegular
}
names := make([]string, 0, artifactSecureReadBatchSize)
for {
dirEntries, readErr := entry.readDirBatch()
if readErr != nil && !errors.Is(readErr, io.EOF) {
return nil, readErr
}
if len(dirEntries) > artifactSecureMaxDirectoryEntries-len(names) {
return nil, ErrArtifactLimitExceeded
}
for _, dirEntry := range dirEntries {
names = append(names, dirEntry.Name())
}
if errors.Is(readErr, io.EOF) {
return names, nil
}
}
}
func (r *artifactSecureRoot) exists(relativePath string) (bool, error) {
parent, name, err := r.openParent(relativePath)
if err != nil {
if isArtifactWindowsNotExist(err) {
return false, nil
}
return false, err
}
defer windows.CloseHandle(parent)
handle, err := artifactNtOpen(
parent,
name,
windows.FILE_READ_ATTRIBUTES|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
if isArtifactWindowsNotExist(err) {
return false, nil
}
return false, translateArtifactWindowsError(err)
}
windows.CloseHandle(handle)
return true, nil
}
func (r *artifactSecureRoot) syncDir(relativePath string) error {
handle, err := r.openDir(relativePath)
if err != nil {
return err
}
defer windows.CloseHandle(handle)
err = windows.FlushFileBuffers(handle)
if errors.Is(err, windows.ERROR_ACCESS_DENIED) || errors.Is(err, windows.ERROR_INVALID_HANDLE) {
return nil
}
return err
}
func (r *artifactSecureRoot) removeTree(relativePath string) error {
parent, name, err := r.openParent(relativePath)
if err != nil {
return err
}
defer windows.CloseHandle(parent)
remaining := artifactSecureCleanupEntryBudget
return removeArtifactTreeAt(parent, name, 0, &remaining)
}
func removeArtifactTreeAt(parent windows.Handle, name string, depth int, remaining *int) error {
if depth > artifactSecureMaxDepth {
return ErrArtifactLimitExceeded
}
if *remaining <= 0 {
return ErrArtifactLimitExceeded
}
*remaining--
handle, err := artifactNtOpen(
parent,
name,
windows.FILE_GENERIC_READ|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
if isArtifactWindowsNotExist(err) {
return nil
}
return translateArtifactWindowsError(err)
}
defer windows.CloseHandle(handle)
var info windows.ByHandleFileInformation
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
return err
}
isDirectory := info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0
isReparsePoint := info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0
if isDirectory && !isReparsePoint {
duplicate, err := duplicateArtifactHandle(handle)
if err != nil {
return err
}
dir := os.NewFile(uintptr(duplicate), name)
for {
entries, readErr := dir.ReadDir(artifactSecureReadBatchSize)
if readErr != nil && !errors.Is(readErr, io.EOF) {
_ = dir.Close()
return readErr
}
for _, entry := range entries {
if err := removeArtifactTreeAt(handle, entry.Name(), depth+1, remaining); err != nil {
_ = dir.Close()
return err
}
}
if errors.Is(readErr, io.EOF) {
break
}
}
if err := dir.Close(); err != nil {
return err
}
}
deleteFlag := byte(1)
return windows.SetFileInformationByHandle(
handle,
windows.FileDispositionInfo,
&deleteFlag,
1,
)
}
func (r *artifactSecureRoot) renameNoReplace(oldName, newName string) error {
return r.rename(oldName, newName, false)
}
func (r *artifactSecureRoot) renameReplace(oldName, newName string) error {
return r.rename(oldName, newName, true)
}
func (r *artifactSecureRoot) rename(oldName, newName string, replace bool) error {
oldPath, err := validateArtifactRelativePath(oldName)
if err != nil {
return err
}
newPath, err := validateArtifactRelativePath(newName)
if err != nil {
return err
}
if path.Dir(oldPath) != "." || path.Dir(newPath) != "." {
return ErrArtifactInvalidPath
}
source, err := artifactNtOpen(
r.handle,
oldPath,
windows.FILE_GENERIC_READ|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
return translateArtifactWindowsError(err)
}
defer windows.CloseHandle(source)
if err := rejectArtifactReparsePoint(source); err != nil {
return err
}
newNameUTF16, err := windows.UTF16FromString(newPath)
if err != nil {
return err
}
nameBytes := (len(newNameUTF16) - 1) * 2
var layout artifactFileRenameInformation
bufferSize := int(unsafe.Offsetof(layout.FileName)) + nameBytes
buffer := make([]byte, bufferSize)
info := (*artifactFileRenameInformation)(unsafe.Pointer(&buffer[0]))
if replace {
info.ReplaceIfExists = 1
}
info.RootDirectory = r.handle
info.FileNameLength = uint32(nameBytes)
copy(
(*[windows.MAX_LONG_PATH]uint16)(unsafe.Pointer(&info.FileName[0]))[:nameBytes/2:nameBytes/2],
newNameUTF16[:len(newNameUTF16)-1],
)
var iosb windows.IO_STATUS_BLOCK
err = windows.NtSetInformationFile(
source,
&iosb,
&buffer[0],
uint32(bufferSize),
windows.FileRenameInformation,
)
if !replace && isArtifactAlreadyExists(err) {
return errors.Join(ErrArtifactPublishConflict, err)
}
return translateArtifactWindowsError(err)
}
func (r *artifactSecureRoot) openParent(relativePath string) (windows.Handle, string, error) {
components, err := artifactPathComponentsWindows(relativePath)
if err != nil {
return windows.InvalidHandle, "", err
}
if len(components) == 0 {
return windows.InvalidHandle, "", ErrArtifactInvalidPath
}
current, err := duplicateArtifactHandle(r.handle)
if err != nil {
return windows.InvalidHandle, "", err
}
for _, component := range components[:len(components)-1] {
next, err := artifactNtOpen(
current,
component,
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
windows.CloseHandle(current)
return windows.InvalidHandle, "", translateArtifactWindowsError(err)
}
if err := rejectArtifactReparsePoint(next); err != nil {
windows.CloseHandle(next)
windows.CloseHandle(current)
return windows.InvalidHandle, "", err
}
windows.CloseHandle(current)
current = next
}
return current, components[len(components)-1], nil
}
func (r *artifactSecureRoot) openDir(relativePath string) (windows.Handle, error) {
if relativePath == "." {
return duplicateArtifactHandle(r.handle)
}
components, err := artifactPathComponentsWindows(relativePath)
if err != nil {
return windows.InvalidHandle, err
}
current, err := duplicateArtifactHandle(r.handle)
if err != nil {
return windows.InvalidHandle, err
}
for _, component := range components {
next, err := artifactNtOpen(
current,
component,
windows.FILE_GENERIC_READ|windows.FILE_GENERIC_WRITE|windows.DELETE|windows.SYNCHRONIZE,
windows.FILE_OPEN,
windows.FILE_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
0,
)
if err != nil {
windows.CloseHandle(current)
return windows.InvalidHandle, translateArtifactWindowsError(err)
}
if err := rejectArtifactReparsePoint(next); err != nil {
windows.CloseHandle(next)
windows.CloseHandle(current)
return windows.InvalidHandle, err
}
windows.CloseHandle(current)
current = next
}
return current, nil
}
func artifactNtOpen(
root windows.Handle,
name string,
access uint32,
disposition uint32,
options uint32,
attributes uint32,
) (windows.Handle, error) {
objectName, err := windows.NewNTUnicodeString(name)
if err != nil {
return windows.InvalidHandle, err
}
oa := &windows.OBJECT_ATTRIBUTES{
Length: uint32(unsafe.Sizeof(windows.OBJECT_ATTRIBUTES{})),
RootDirectory: root,
ObjectName: objectName,
Attributes: windows.OBJ_CASE_INSENSITIVE,
}
var handle windows.Handle
var iosb windows.IO_STATUS_BLOCK
var allocationSize int64
err = windows.NtCreateFile(
&handle,
access,
oa,
&iosb,
&allocationSize,
attributes,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
disposition,
options,
0,
0,
)
return handle, err
}
func duplicateArtifactHandle(source windows.Handle) (windows.Handle, error) {
var target windows.Handle
err := windows.DuplicateHandle(
windows.CurrentProcess(),
source,
windows.CurrentProcess(),
&target,
0,
false,
windows.DUPLICATE_SAME_ACCESS,
)
return target, err
}
func rejectArtifactReparsePoint(handle windows.Handle) error {
var info windows.ByHandleFileInformation
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
return err
}
if info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return errArtifactReparsePoint
}
return nil
}
func artifactNTAbsolutePath(absolute string) string {
if strings.HasPrefix(absolute, `\\`) {
return `\??\UNC\` + strings.TrimPrefix(absolute, `\\`)
}
return `\??\` + absolute
}
func artifactPathComponentsWindows(relativePath string) ([]string, error) {
if relativePath == "." {
return nil, nil
}
clean, err := validateArtifactRelativePath(relativePath)
if err != nil {
return nil, err
}
return strings.Split(clean, "/"), nil
}
func translateArtifactWindowsError(err error) error {
if err == nil {
return nil
}
return err
}
func isArtifactWindowsNotExist(err error) bool {
return errors.Is(err, windows.ERROR_FILE_NOT_FOUND) ||
errors.Is(err, windows.ERROR_PATH_NOT_FOUND) ||
errors.Is(err, windows.STATUS_OBJECT_NAME_NOT_FOUND) ||
errors.Is(err, windows.STATUS_OBJECT_PATH_NOT_FOUND)
}
func isArtifactAlreadyExists(err error) bool {
return errors.Is(err, windows.ERROR_ALREADY_EXISTS) ||
errors.Is(err, windows.ERROR_FILE_EXISTS) ||
errors.Is(err, windows.STATUS_OBJECT_NAME_COLLISION) ||
errors.Is(err, ErrArtifactPublishConflict)
}
func isArtifactNotExist(err error) bool {
return isArtifactWindowsNotExist(err)
}