Skip to content

--no-recurse-submodules placed before subcommand → fails with exit 129; defense-in-depth gap #800

@jatenner

Description

@jatenner

--no-recurse-submodules placed before subcommand → fails with exit 129; defense-in-depth gap

Summary

src/core/git-remote.ts declares --no-recurse-submodules as part of GIT_SSRF_FLAGS, which is spread into args before the subcommand (clone or pull). Git rejects --no-recurse-submodules as a top-level flag with exit 129. Both cloneRepo and pullRepo are affected.

Reproduction

$ git --no-recurse-submodules clone --help
unknown option: --no-recurse-submodules

$ git -C some-repo --no-recurse-submodules pull --ff-only
unknown option: --no-recurse-submodules

In a working brain checkout (gbrain 0.30.3):

$ /root/gbrain-env.sh gbrain sync --repo /root/brain --dry-run
Warning: git pull failed: git pull failed in /root/brain: Command failed: git -C /root/brain -c http.followRedirects=false -c

(Truncation in src/commands/sync.ts:428 (msg.slice(0, 100)) hides the real cause; running the same command outside gbrain reveals the unknown option error.)

Impact

  1. Cron noise. Every gbrain-sync run that does a remote-pull emits a truncated warning, and falls back to "sync from local state".
  2. Defense-in-depth gap on clone. The comment above GIT_SSRF_FLAGS says --no-recurse-submodules is meant so that .gitmodules cannot become a second fetch surface. Because the flag is misplaced, cloneRepo actually clones submodules normally if the upstream repo has any. The SSRF posture documented in the comment is not what's running.

Proposed fix

Move --no-recurse-submodules out of GIT_SSRF_FLAGS (which holds top-level -c flags) and into each subcommand's args after the subcommand verb:

 /**
- * SSRF-defensive flag set. Used by both cloneRepo and pullRepo.
- * - http.followRedirects=false: closes DNS rebinding via redirect chains
- * - protocol.file.allow=never: no local-file URLs (defense in depth)
- * - protocol.ext.allow=never: no external helpers (`git-remote-foo`)
- * - --no-recurse-submodules: .gitmodules cannot become a second fetch surface
+ * SSRF-defensive flag set. Used by both cloneRepo and pullRepo as top-level
+ * git flags (i.e., before the subcommand). `--no-recurse-submodules` is NOT
+ * a valid top-level git flag — git rejects it with exit 129 — so subcommand
+ * args (clone/pull) append it themselves.
+ * - http.followRedirects=false: closes DNS rebinding via redirect chains
+ * - protocol.file.allow=never: no local-file URLs (defense in depth)
+ * - protocol.ext.allow=never: no external helpers (`git-remote-foo`)
  */
 export const GIT_SSRF_FLAGS = [
   '-c', 'http.followRedirects=false',
   '-c', 'protocol.file.allow=never',
   '-c', 'protocol.ext.allow=never',
-  '--no-recurse-submodules',
 ] as const;

-  const args: string[] = [...GIT_SSRF_FLAGS, 'clone'];
+  const args: string[] = [...GIT_SSRF_FLAGS, 'clone', '--no-recurse-submodules'];

-  const args: string[] = ['-C', repoPath, ...GIT_SSRF_FLAGS, 'pull', '--ff-only'];
+  const args: string[] = ['-C', repoPath, ...GIT_SSRF_FLAGS, 'pull', '--ff-only', '--no-recurse-submodules'];

Test coverage suggestion: test/git-remote.test.ts (or wherever pullRepo / cloneRepo get unit-covered) could spy on execFileSync and assert that --no-recurse-submodules appears AFTER the subcommand name in the args array.

Bonus finding (different bug, related path)

While investigating, I noticed protocol.file.allow=never in GIT_SSRF_FLAGS blocks pullRepo for repos whose origin is a file:// URL (or local bare-repo path). Local-file origins are common for backup mirrors and sneakernet workflows. For cloneRepo the flag is intentional (don't clone untrusted file:// remotes). For pullRepo of an already-trusted, already-cloned repo, the same defense doesn't add much — the origin URL was vetted at clone time.

Possible fix (separate from the main one): split GIT_SSRF_FLAGS_CLONE (full set) and GIT_SSRF_FLAGS_PULL (omit protocol.file.allow=never). Or detect file:///local-path origins in pullRepo and skip the flag for those. Happy to file separately if you want.

Environment

  • gbrain --version: 0.30.3
  • Branch: garrytan/copenhagen-v3 HEAD c1e2a6d (this branch was used because it has the v0.29.1 effective-date migration fixes that aren't yet on master)
  • Bug also present on origin/master (89ae720, v0.31.0). Same code in git-remote.ts.
  • Tested against git 2.x (Ubuntu 24.04 / /usr/bin/git).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions