Initial commit on Forgejo
Some checks failed
CI and releases / Test and vet (push) Failing after 1s
CI and releases / Build release binaries (push) Has been skipped
CI and releases / Publish GitHub release (push) Has been skipped

Fresh repository history for elektrine/tarakan-client hosted at
https://git.elektrine.com/elektrine/tarakan-client.
This commit is contained in:
Maxfield Luke 2026-07-29 04:56:36 -04:00
commit 58b29a7f84
70 changed files with 12994 additions and 0 deletions

View file

@ -0,0 +1,240 @@
package snapshot
import (
"crypto/sha256"
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
var (
ErrCommitUnavailable = errors.New("pinned commit is not available in the local repository")
fullCommitPattern = regexp.MustCompile(`^[0-9a-fA-F]{40}$`)
)
// Snapshot is a disposable, metadata-free copy of one exact Git commit.
type Snapshot struct {
Root string
base string
baseline [sha256.Size]byte
}
// Create materializes commit from repositoryRoot without sharing Git object
// hardlinks. Hooks, global Git configuration, and interactive credential
// prompts are disabled for every Git subprocess involved in the snapshot.
func Create(repositoryRoot, commit string) (*Snapshot, error) {
if !fullCommitPattern.MatchString(commit) {
return nil, errors.New("task commit must be a full 40-character hexadecimal SHA")
}
if err := gitAvailable(repositoryRoot, commit); err != nil {
return nil, err
}
base, err := os.MkdirTemp("", "tarakan-snapshot-")
if err != nil {
return nil, fmt.Errorf("create snapshot directory: %w", err)
}
cleanup := true
defer func() {
if cleanup {
_ = os.RemoveAll(base)
}
}()
home := filepath.Join(base, "home")
if err := os.Mkdir(home, 0o700); err != nil {
return nil, fmt.Errorf("create isolated Git home: %w", err)
}
root := filepath.Join(base, "repository")
if output, err := runGit(base, home, "-c", "core.hooksPath=/dev/null", "clone", "--local", "--no-hardlinks", "--no-checkout", "--", repositoryRoot, root); err != nil {
return nil, fmt.Errorf("clone repository snapshot: %w: %s", err, strings.TrimSpace(output))
}
if output, err := runGit(base, home,
"-C", root,
"-c", "core.hooksPath=/dev/null",
"-c", "filter.lfs.smudge=",
"-c", "filter.lfs.required=false",
"checkout", "--detach", "--force", commit,
); err != nil {
return nil, fmt.Errorf("check out pinned commit: %w: %s", err, strings.TrimSpace(output))
}
// Absolute or escaping symlinks would let the agent read host paths.
// Neutralize them (replace with a small text file) instead of failing:
// many real repos ship broken absolute "external/" pointers from another
// machine (e.g. /Users/... on Linux), and the review should still run.
if err := neutralizeExternalSymlinks(root); err != nil {
return nil, err
}
if err := os.RemoveAll(filepath.Join(root, ".git")); err != nil {
return nil, fmt.Errorf("remove snapshot Git metadata: %w", err)
}
baseline, err := digestTree(root)
if err != nil {
return nil, fmt.Errorf("hash repository snapshot: %w", err)
}
cleanup = false
return &Snapshot{Root: root, base: base, baseline: baseline}, nil
}
func neutralizeExternalSymlinks(root string) error {
return filepath.WalkDir(root, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.Type()&os.ModeSymlink == 0 {
return nil
}
target, err := os.Readlink(path)
if err != nil {
return err
}
if !isExternalSymlink(root, path, target) {
return nil
}
rel, relErr := filepath.Rel(root, path)
if relErr != nil {
rel = path
}
if err := os.Remove(path); err != nil {
return fmt.Errorf("remove external symlink %s: %w", filepath.ToSlash(rel), err)
}
// Regular file: agent cannot follow it to host content; original
// target is preserved as text so reviewers still see the pointer.
body := "tarakan-snapshot: neutralized external symlink\noriginal-target: " + target + "\n"
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
return fmt.Errorf("neutralize external symlink %s: %w", filepath.ToSlash(rel), err)
}
return nil
})
}
// isExternalSymlink is true for absolute targets or relative targets that
// resolve outside the snapshot root.
func isExternalSymlink(root, path, target string) bool {
if filepath.IsAbs(target) {
return true
}
resolved := filepath.Clean(filepath.Join(filepath.Dir(path), target))
relative, err := filepath.Rel(root, resolved)
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return true
}
return false
}
// Changed reports whether any path, mode, symlink target, or regular-file
// content differs from the snapshot presented to the agent.
func (s *Snapshot) Changed() (bool, error) {
current, err := digestTree(s.Root)
if err != nil {
return false, err
}
return current != s.baseline, nil
}
// Close destroys the snapshot. It is safe to call more than once.
func (s *Snapshot) Close() error {
if s == nil || s.base == "" {
return nil
}
base := s.base
s.base = ""
return os.RemoveAll(base)
}
func gitAvailable(repositoryRoot, commit string) error {
command := exec.Command("git", "-C", repositoryRoot, "cat-file", "-e", commit+"^{commit}")
command.Env = gitEnvironment(os.TempDir())
if err := command.Run(); err != nil {
return fmt.Errorf("%w: %s (run git fetch origin first)", ErrCommitUnavailable, commit)
}
return nil
}
func runGit(directory, home string, arguments ...string) (string, error) {
command := exec.Command("git", arguments...)
command.Dir = directory
command.Env = gitEnvironment(home)
output, err := command.CombinedOutput()
return string(output), err
}
func gitEnvironment(home string) []string {
filtered := make([]string, 0, len(os.Environ())+6)
for _, entry := range os.Environ() {
name, _, _ := strings.Cut(entry, "=")
switch strings.ToUpper(name) {
case "HOME", "XDG_CONFIG_HOME", "GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM", "GIT_CONFIG_NOSYSTEM", "GIT_TERMINAL_PROMPT", "GIT_ASKPASS", "SSH_ASKPASS":
continue
}
if strings.HasPrefix(strings.ToUpper(name), "TARAKAN_") {
continue
}
filtered = append(filtered, entry)
}
return append(filtered,
"HOME="+home,
"XDG_CONFIG_HOME="+filepath.Join(home, ".config"),
"GIT_CONFIG_NOSYSTEM=1",
"GIT_CONFIG_GLOBAL=/dev/null",
"GIT_TERMINAL_PROMPT=0",
)
}
func digestTree(root string) ([sha256.Size]byte, error) {
hash := sha256.New()
err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
relative, err := filepath.Rel(root, path)
if err != nil {
return err
}
info, err := entry.Info()
if err != nil {
return err
}
_, _ = io.WriteString(hash, filepath.ToSlash(relative))
_, _ = io.WriteString(hash, "\x00"+info.Mode().String()+"\x00")
switch {
case info.Mode()&os.ModeSymlink != 0:
target, err := os.Readlink(path)
if err != nil {
return err
}
_, _ = io.WriteString(hash, target)
case info.Mode().IsRegular():
file, err := os.Open(path)
if err != nil {
return err
}
_, copyErr := io.Copy(hash, file)
closeErr := file.Close()
if copyErr != nil {
return copyErr
}
if closeErr != nil {
return closeErr
}
}
_, _ = io.WriteString(hash, "\x00")
return nil
})
if err != nil {
return [sha256.Size]byte{}, err
}
var result [sha256.Size]byte
copy(result[:], hash.Sum(nil))
return result, nil
}

View file

@ -0,0 +1,140 @@
package snapshot
import (
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestCreatePinsCommitWithoutGitMetadataAndDetectsChanges(t *testing.T) {
repository := createTestRepository(t)
commit := gitOutput(t, repository, "rev-parse", "HEAD")
snapshot, err := Create(repository, commit)
if err != nil {
t.Fatal(err)
}
root := snapshot.Root
t.Cleanup(func() { _ = snapshot.Close() })
if _, err := os.Stat(filepath.Join(root, ".git")); !os.IsNotExist(err) {
t.Fatalf("snapshot retains Git metadata: %v", err)
}
changed, err := snapshot.Changed()
if err != nil || changed {
t.Fatalf("unchanged snapshot: changed=%v err=%v", changed, err)
}
if err := os.WriteFile(filepath.Join(root, "README.md"), []byte("changed\n"), 0o600); err != nil {
t.Fatal(err)
}
changed, err = snapshot.Changed()
if err != nil || !changed {
t.Fatalf("changed snapshot: changed=%v err=%v", changed, err)
}
if err := snapshot.Close(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(root); !os.IsNotExist(err) {
t.Fatalf("snapshot was not destroyed: %v", err)
}
}
func TestCreateRejectsUnavailableCommit(t *testing.T) {
repository := createTestRepository(t)
_, err := Create(repository, "dead000000000000000000000000000000000000")
if !errors.Is(err, ErrCommitUnavailable) {
t.Fatalf("error = %v", err)
}
}
func TestCreateNeutralizesExternalAndEscapingSymlinks(t *testing.T) {
repository := createTestRepository(t)
// Absolute external pointer (common "works on my Mac" vendor path).
if err := os.Symlink("/Users/someone/Desktop/secret", filepath.Join(repository, "external-abs")); err != nil {
t.Fatal(err)
}
// Relative path that escapes the tree.
if err := os.Symlink("../host-secret", filepath.Join(repository, "escape")); err != nil {
t.Fatal(err)
}
// Safe in-tree relative link must be kept.
if err := os.Symlink("README.md", filepath.Join(repository, "readme-link")); err != nil {
t.Fatal(err)
}
runGitTest(t, repository, "add", "external-abs", "escape", "readme-link")
runGitTest(t, repository, "-c", "user.name=Tarakan Test", "-c", "user.email=test@tarakan.invalid", "commit", "-m", "symlinks")
commit := gitOutput(t, repository, "rev-parse", "HEAD")
snap, err := Create(repository, commit)
if err != nil {
t.Fatalf("Create should neutralize external symlinks, got: %v", err)
}
t.Cleanup(func() { _ = snap.Close() })
// Escaping / absolute links become ordinary files with the original target.
for _, name := range []string{"external-abs", "escape"} {
info, err := os.Lstat(filepath.Join(snap.Root, name))
if err != nil {
t.Fatal(err)
}
if info.Mode()&os.ModeSymlink != 0 {
t.Fatalf("%s still a symlink", name)
}
body, err := os.ReadFile(filepath.Join(snap.Root, name))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(body), "neutralized external symlink") {
t.Fatalf("%s body = %q", name, body)
}
}
// In-tree relative symlink preserved.
target, err := os.Readlink(filepath.Join(snap.Root, "readme-link"))
if err != nil {
t.Fatalf("safe symlink should remain: %v", err)
}
if target != "README.md" {
t.Fatalf("safe symlink target = %q", target)
}
changed, err := snap.Changed()
if err != nil || changed {
t.Fatalf("unchanged after neutralize: changed=%v err=%v", changed, err)
}
}
func createTestRepository(t *testing.T) string {
t.Helper()
repository := t.TempDir()
runGitTest(t, repository, "init", "-b", "main")
if err := os.WriteFile(filepath.Join(repository, "README.md"), []byte("pinned\n"), 0o600); err != nil {
t.Fatal(err)
}
runGitTest(t, repository, "add", "README.md")
runGitTest(t, repository, "-c", "user.name=Tarakan Test", "-c", "user.email=test@tarakan.invalid", "commit", "-m", "initial")
return repository
}
func runGitTest(t *testing.T, directory string, arguments ...string) {
t.Helper()
command := exec.Command("git", append([]string{"-C", directory}, arguments...)...)
if output, err := command.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v: %s", arguments, err, output)
}
}
func gitOutput(t *testing.T, directory string, arguments ...string) string {
t.Helper()
command := exec.Command("git", append([]string{"-C", directory}, arguments...)...)
output, err := command.Output()
if err != nil {
t.Fatalf("git %v: %v", arguments, err)
}
return strings.TrimSpace(string(output))
}