package agentguard import ( "fmt" "os" "path/filepath" "strings" ) const maxGitPointerBytes = 64 * 1024 type gitMetadataError struct { code BlockerCode message string } func (e *gitMetadataError) Error() string { return e.message } func discoverGitMetadata(taskRoot string, mode IsolationMode) ([]canonicalPath, *gitMetadataError) { dotGit := filepath.Join(taskRoot, ".git") info, err := os.Lstat(dotGit) if err != nil { if os.IsNotExist(err) && mode == IsolationModeOverlay { return nil, nil } return nil, &gitMetadataError{ code: BlockerCodeWorkspaceNotCanonical, message: "task isolation is missing the Git metadata required by its mode", } } if info.Mode()&os.ModeSymlink != 0 { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "task .git entry cannot be a symbolic link", } } if info.IsDir() { if mode == IsolationModeWorktree { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree isolation requires a .git pointer file", } } metadata, canonicalErr := canonicalDirectory(dotGit, false) if canonicalErr != nil || !containsPath(taskRoot, metadata.path) { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "full-clone Git metadata must remain inside the task root", } } return []canonicalPath{metadata}, nil } if mode == IsolationModeClone { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "full-clone isolation requires an internal .git directory", } } if !info.Mode().IsRegular() { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "task .git entry has an unsupported file type", } } return parseGitPointer(taskRoot, dotGit) } func discoverEffectiveGitMetadata(taskRoot, workingDir string) ([]canonicalPath, *gitMetadataError) { curr := workingDir for curr != taskRoot && containsPath(taskRoot, curr) { dotGit := filepath.Join(curr, ".git") info, err := os.Lstat(dotGit) if err == nil { if info.Mode()&os.ModeSymlink != 0 { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "task .git entry cannot be a symbolic link", } } if info.IsDir() { metadata, canonicalErr := canonicalDirectory(dotGit, false) if canonicalErr != nil { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "nested Git metadata is unavailable", } } return []canonicalPath{metadata}, nil } if !info.Mode().IsRegular() { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "task .git entry has an unsupported file type", } } return parseGitPointer(curr, dotGit) } if !os.IsNotExist(err) { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "nested Git metadata is inaccessible", } } parent := filepath.Dir(curr) if parent == curr { break } curr = parent } return nil, nil } func parseGitPointer(parentDir, dotGit string) ([]canonicalPath, *gitMetadataError) { gitDirValue, readErr := readPointerFile(dotGit, "gitdir:") if readErr != nil { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree .git pointer is invalid", } } gitDir, canonicalErr := canonicalDirectory(resolvePointer(parentDir, gitDirValue), false) if canonicalErr != nil { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree Git directory is unavailable", } } metadata := []canonicalPath{gitDir} commonPath := filepath.Join(gitDir.path, "commondir") if commonInfo, statErr := os.Lstat(commonPath); statErr == nil { if !commonInfo.Mode().IsRegular() || commonInfo.Mode()&os.ModeSymlink != 0 { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree common-dir pointer is invalid", } } commonValue, pointerErr := readPointerFile(commonPath, "") if pointerErr != nil { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree common-dir pointer is invalid", } } commonDir, commonErr := canonicalDirectory(resolvePointer(gitDir.path, commonValue), false) if commonErr != nil { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree common Git directory is unavailable", } } if commonDir.path != gitDir.path { metadata = append(metadata, commonDir) } } else if !os.IsNotExist(statErr) { return nil, &gitMetadataError{ code: BlockerCodeVCSMetadataNotAllowed, message: "worktree common-dir metadata is inaccessible", } } return metadata, nil } func readPointerFile(path, prefix string) (string, error) { content, err := os.ReadFile(path) if err != nil { return "", err } if len(content) > maxGitPointerBytes { return "", fmt.Errorf("pointer file is too large") } value := strings.TrimSpace(string(content)) if prefix != "" { if !strings.HasPrefix(strings.ToLower(value), prefix) { return "", fmt.Errorf("missing %s prefix", prefix) } value = strings.TrimSpace(value[len(prefix):]) } if value == "" || containsParentReference(value) && filepath.IsAbs(value) { return "", fmt.Errorf("empty or invalid pointer") } return value, nil } func resolvePointer(parent, value string) string { if filepath.IsAbs(value) { return filepath.Clean(value) } return filepath.Clean(filepath.Join(parent, value)) }