From d9b4d08baaefbb03891fef8e3a3c58cccc5de3aa Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sat, 19 Sep 2026 20:17:03 +0500 Subject: [PATCH 1/4] fix(discovery): skip hidden directories during workspace scans Dot-prefixed directories are tool state, caches, and generated fixtures rather than portfolio checkouts, so scanning them manufactured hundreds of anchor findings for synthetic trees. Keep .git as the discoverable boundary while skipping every other dot-directory. --- core/discovery/local.go | 10 ++++++++-- core/discovery/local_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/discovery/local.go b/core/discovery/local.go index e6d4a68..0c585b2 100644 --- a/core/discovery/local.go +++ b/core/discovery/local.go @@ -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 diff --git a/core/discovery/local_test.go b/core/discovery/local_test.go index 312ae78..347e937 100644 --- a/core/discovery/local_test.go +++ b/core/discovery/local_test.go @@ -190,3 +190,28 @@ func TestDiscoverAcceptsIdentityPinnedBySuperprojectAndCheckedOutStandalone(t *t } } } + +func TestDiscoverSkipsHiddenDirectoryTrees(t *testing.T) { + t.Parallel() + root := t.TempDir() + 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) + } +} From 5af71983b0db52121bd8d8a3ffba2f4709db9849 Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sat, 19 Sep 2026 20:17:03 +0500 Subject: [PATCH 2/4] fix(providers/git): observe quarantine remotes read-only Checkout quarantine only needs ls-remote to prove the head is published, so gating it on the push URL blocked SSH and HTTPS remotes entirely. Validate the fetch URL for observation instead and permit the credential -free https/ssh transports for the read while push stays gated. --- core/providers/git/handoff.go | 5 +++- core/providers/git/quarantine.go | 6 +++- core/providers/git/quarantine_test.go | 43 +++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/core/providers/git/handoff.go b/core/providers/git/handoff.go index e8d5c7c..5956b25 100644 --- a/core/providers/git/handoff.go +++ b/core/providers/git/handoff.go @@ -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 { diff --git a/core/providers/git/quarantine.go b/core/providers/git/quarantine.go index b72ef5f..3cb25da 100644 --- a/core/providers/git/quarantine.go +++ b/core/providers/git/quarantine.go @@ -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 } diff --git a/core/providers/git/quarantine_test.go b/core/providers/git/quarantine_test.go index d1248d9..77295f7 100644 --- a/core/providers/git/quarantine_test.go +++ b/core/providers/git/quarantine_test.go @@ -3,6 +3,7 @@ package git import ( "context" "crypto/sha256" + "errors" "fmt" "os" "path/filepath" @@ -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) + } +} From 298e14b3e7919bf79102fe937bf359e0dd792919 Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sat, 19 Sep 2026 20:25:43 +0500 Subject: [PATCH 3/4] chore(ci): restamp development lock for the fixed source tree --- .gds/bundle.lock.yaml | 10 +++++----- .github/workflows/gds-ci.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index 37203d4..342eced 100644 --- a/.gds/bundle.lock.yaml +++ b/.gds/bundle.lock.yaml @@ -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" diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index b892596..253e0b6 100644 --- a/.github/workflows/gds-ci.yml +++ b/.github/workflows/gds-ci.yml @@ -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 From 150b6fc0a9cbd34459215e340e0481e6af56a40e Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Sat, 19 Sep 2026 20:38:39 +0500 Subject: [PATCH 4/4] test(discovery): canonicalize tempdir root for darwin /var symlink --- core/discovery/local_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/discovery/local_test.go b/core/discovery/local_test.go index 347e937..0092c4d 100644 --- a/core/discovery/local_test.go +++ b/core/discovery/local_test.go @@ -193,7 +193,12 @@ func TestDiscoverAcceptsIdentityPinnedBySuperprojectAndCheckedOutStandalone(t *t func TestDiscoverSkipsHiddenDirectoryTrees(t *testing.T) { t.Parallel() - root := t.TempDir() + // 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} {