iop/packages/go/agentguard/gitmeta.go

194 lines
5.5 KiB
Go

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))
}