Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ bundle:
version: "0.9.6-dev"
release_sequence: 0
channel: "development"
source_tree_digest: "sha256:6799d517ad06faba9cc1edc3830dd54d0a890f3573bc1f60a26232f5d1926524"
digest: "sha256:db7dae4282ef3d69f0375c9e54feada9dc11f219d64e591a76559883ea598f0e"
source_tree_digest: "sha256:b9d572eb73bb14ae8ede20dfc1e9317046dcb544270eff95153746f93f094820"
digest: "sha256:a45453995b649d082b39753c1d7eff869d2dcad5180532cb14f85435fae2a7fa"

projection:
input_digest: "sha256:37d63f73feba8549ec22aa97b81b7cc0d53b701d73b36661625c0bb61da53998"
output_digest: "sha256:508542eaec283ca3e14bb78c495c0c52a45ebb85b24cce2e104f8655ad1b0bb1"
input_digest: "sha256:90af33f4abbb808f5567e074347f17822cc610029e11a835fb5a8918c376ec9b"
output_digest: "sha256:080add7de51702df727bb8f1b770dcd5fd8343000d8b249af94a7b342a0a240b"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:211668dd85e9d4bd3f547a1ea0cc9b1a5f486c64bde02a328b53dc3ef665c9fd"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:2e18aafadceafafab54dbf21fd0f68b50c9618aed05d8f2eb39e161ef999c287"
digest: "sha256:eef6659ecb3cd40914b73fde9856ad2bfa96f106ceac52f8c2fda0ef4fc00a79"
4 changes: 2 additions & 2 deletions .github/workflows/gds-ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED FILE - DO NOT EDIT DIRECTLY
# generator: gds
# bundle: 0.9.6-dev
# source-tree-digest: sha256:6799d517ad06faba9cc1edc3830dd54d0a890f3573bc1f60a26232f5d1926524
# input-digest: sha256:37d63f73feba8549ec22aa97b81b7cc0d53b701d73b36661625c0bb61da53998
# source-tree-digest: sha256:b9d572eb73bb14ae8ede20dfc1e9317046dcb544270eff95153746f93f094820
# input-digest: sha256:90af33f4abbb808f5567e074347f17822cc610029e11a835fb5a8918c376ec9b
# output-digest: sha256:b9bf3d0c64c0fb371596e7d090e82e62aebbfde91929115fc15fb28644e4fd38
# edit-source:
# - .gds/repository.yaml
Expand Down
10 changes: 8 additions & 2 deletions core/discovery/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,15 @@ func pathDepth(relative string) int {
}

func excludedDirectory(name string) bool {
// Hidden directories other than a repository's own .git boundary are tool
// state, caches, or generated fixtures (".tmp", ".idea", ".venv", ...) —
// never portfolio checkouts. Scanning them manufactures anchor findings
// for synthetic trees and burns time on ignored content.
if name != ".git" && strings.HasPrefix(name, ".") {
return true
}
switch name {
case ".cache", ".idea", ".pytest_cache", ".ruff_cache", ".tox", ".venv",
"__pycache__", "node_modules", "target", "vendor":
case "__pycache__", "node_modules", "target", "vendor":
return true
default:
return false
Expand Down
30 changes: 30 additions & 0 deletions core/discovery/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,33 @@ func TestDiscoverAcceptsIdentityPinnedBySuperprojectAndCheckedOutStandalone(t *t
}
}
}

func TestDiscoverSkipsHiddenDirectoryTrees(t *testing.T) {
t.Parallel()
// Boundaries are reported canonicalized: on macOS t.TempDir() lives under
// /var, which is a symlink to /private/var.
root, err := filepath.EvalSymlinks(t.TempDir())
if err != nil {
t.Fatal(err)
}
plain := filepath.Join(root, "plain")
fixture := filepath.Join(root, ".tmp", "native-linux", "fixture-repo")
for _, path := range []string{plain, fixture} {
if err := os.MkdirAll(path, 0o755); err != nil {
t.Fatal(err)
}
if output, err := exec.Command("git", "init", "-q", path).CombinedOutput(); err != nil {
t.Fatalf("git init %s: %v\n%s", path, err, output)
}
}

result, err := newTestDiscovery(t).Discover(
context.Background(), root, Options{MaxDepth: 8, MaxRepositories: 10, Concurrency: 2},
)
if err != nil {
t.Fatalf("Discover() error = %v", err)
}
if len(result.Boundaries) != 1 || result.Boundaries[0].Path != plain {
t.Fatalf("boundaries = %#v, want only %s", result.Boundaries, plain)
}
}
5 changes: 4 additions & 1 deletion core/providers/git/handoff.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,10 @@ func (runner *MutationRunner) observeRemoteRef(
) (string, error) {
result, err := runner.runWithEnvironment(
ctx, root, map[int]struct{}{0: {}}, nil,
"-c", "protocol.allow=never", "-c", "protocol.file.allow=always",
"-c", "protocol.allow=never",
"-c", "protocol.file.allow=always",
"-c", "protocol.https.allow=always",
"-c", "protocol.ssh.allow=always",
"ls-remote", "--refs", remoteURL, remoteRef,
)
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion core/providers/git/quarantine.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ func (runner *MutationRunner) observeCheckoutForQuarantine(
status.Worktrees[0].Locked || status.Worktrees[0].Prunable {
return CheckoutQuarantineEvidence{}, errors.New("checkout is not clean, current, and exclusive")
}
remoteURL, err := runner.validatedPushURL(ctx, physical, "origin")
// Quarantine only reads the remote (ls-remote below) to prove the checkout's
// commits are published; it never pushes. The fetch-side URL is therefore
// the right one to observe and network transports are allowed — the
// mutation-only gate (validatedPushURL) does not apply to a read.
_, remoteURL, err := runner.validatedRemoteURL(ctx, physical, "origin")
if err != nil {
return CheckoutQuarantineEvidence{}, err
}
Expand Down
43 changes: 43 additions & 0 deletions core/providers/git/quarantine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package git
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -75,3 +76,45 @@ func TestQuarantineCheckoutPreservesDirtyRepository(t *testing.T) {
t.Fatalf("dirty work changed: %q %v", content, err)
}
}

func TestQuarantineRemoteValidationAdmitsNetworkTransportsForReadOnlyObservation(t *testing.T) {
client, _ := mutationRepository(t)
runFetchGit(t, client, "remote", "add", "origin", "git@github.com:example/repository.git")
runner, err := NewMutationRunner()
if err != nil {
t.Fatal(err)
}
_, url, err := runner.validatedRemoteURL(context.Background(), client, "origin")
if err != nil {
t.Fatalf("read-only observation must admit the fetch URL: %v", err)
}
if url != "git@github.com:example/repository.git" {
t.Fatalf("url=%q", url)
}
if _, err := runner.validatedPushURL(context.Background(), client, "origin"); !errors.Is(err, ErrNetworkMutationDisabled) {
t.Fatalf("push URL must remain mutation-gated: %v", err)
}
}

func TestQuarantineCheckoutNetworkRemoteFailsAtObservationNotAtTheMutationGate(t *testing.T) {
fixture := fastForwardFixture(t)
runFetchGit(t, fixture.client, "remote", "set-url", "origin", "ssh://127.0.0.1:1/repository.git")
head := stringsTrim(runFetchGit(t, fixture.client, "rev-parse", "HEAD"))
workspaceRoot := filepath.Dir(fixture.client)
stateRoot := t.TempDir()
quarantine := filepath.Join(stateRoot, "quarantine", "checkouts", "repo_fixture", head)
runner, err := NewMutationRunner()
if err != nil {
t.Fatal(err)
}
_, err = runner.QuarantineCheckout(
context.Background(), workspaceRoot, fixture.client, stateRoot, quarantine,
head, "refs/heads/main", fmt.Sprintf("sha256:%x", sha256.Sum256([]byte("x"))),
)
if err == nil {
t.Fatal("an unreachable remote must fail the observation")
}
if errors.Is(err, ErrNetworkMutationDisabled) {
t.Fatalf("read-only remote observation must not hit the mutation gate: %v", err)
}
}
Loading