Skip to content

refactor(git)!: stop injecting FERRFLOW_TOKEN into remote URL + gix foundation - #486

Merged
BryanFRD merged 1 commit into
mainfrom
feat/gix-full-migration
May 21, 2026
Merged

BryanFRD merged 1 commit into
mainfrom
feat/gix-full-migration

Conversation

@BryanFRD

Copy link
Copy Markdown
Contributor

Closes #485
Builds on #479 (full gix migration plan)

Summary

  • Security hardening: removed `authenticated_remote_url()` which baked `FERRFLOW_TOKEN` into the remote URL. Shell-out path now uses `-c credential.helper='...'` (token via stdin, never argv). libgit2 callback path keeps the existing `credentials_callback` (already correct).
  • gix foundation: re-added gix 0.83 with the right feature flags (`sha1` + `max-performance-safe` — `sha1` is required for gix-hash to compile on rustc 1.94, the cause of the chore(deps): bump gix from 0.74.1 to 0.83.0 #481 compile failure). Migrated `collect_all_tags` to gix as the first read-path migration. Remaining libgit2 call sites still use libgit2 — to be migrated incrementally in follow-up PRs.
  • CVE: pulls in gix-date 0.15.3, resolving RUSTSEC-2025-0140.

Why this is a breaking change

The internal auth helper API changed (`get_authenticated_remote` -> `get_remote`), but the user-facing env-var contract (`FERRFLOW_TOKEN`, `GITHUB_TOKEN`, `GITLAB_TOKEN`) is unchanged. Marked breaking because any downstream consumer importing `ferrflow::git::auth::*` will see renamed symbols.

Test plan

  • 517 lib unit tests pass
  • 6 new auth tests (token_for_url variants, configure_git_command inline helper, no-token noop) — env-isolated via Mutex
  • `cargo clippy --features cli -- -D warnings` clean
  • gix 0.83 compiles with selective features (no chore(deps): bump gix from 0.74.1 to 0.83.0 #481 compile error)
  • Verify release-bot flow against a real GitHub remote in CI

…otocol

The previous auth path baked the FERRFLOW_TOKEN into the remote URL
(authenticated_remote_url -> https://x-access-token:TOKEN@github.com/...).
That leaks the token into git's process argv and into any subprocess
error message, and bypasses the standard credential helper protocol.

This change removes authenticated_remote_url entirely. The libgit2
callback path (push, fetch, ls-refs) keeps relying on credentials_callback
which already handles FERRFLOW_TOKEN -> Cred::userpass_plaintext, with
fallback to Cred::credential_helper for users with a configured helper.
The shell-out path (git ls-remote, git push for tags) now uses an inline
-c credential.helper=... config that emits username/password on stdin,
so the token never appears in argv or in the URL.

Also wires gix 0.83 into the cli feature with the sha1 + max-performance-safe
features (required for gix-hash to compile on rustc 1.94), and migrates
collect_all_tags to gix as the first read-path migration. The remaining
git paths still use libgit2 - to be migrated incrementally.

Pulls in gix-date 0.15.3, resolving RUSTSEC-2025-0140.
Copilot AI review requested due to automatic review settings May 21, 2026 06:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 40af923 Previous: 0b5fe57 Ratio
version_files/toml_read 7977 ns/iter (± 91) 8489 ns/iter (± 791) 0.94
version_files/toml_write 180493 ns/iter (± 30183) 132036 ns/iter (± 10286) 1.37
version_files/json_read 5445 ns/iter (± 73) 5314 ns/iter (± 69) 1.02
version_files/json_write 171106 ns/iter (± 24211) 130851 ns/iter (± 8755) 1.31
version_files/xml_read 5101 ns/iter (± 41) 5140 ns/iter (± 16) 0.99
version_files/xml_write 131818 ns/iter (± 13632) 138826 ns/iter (± 18123) 0.95
version_files/gradle_read 4935 ns/iter (± 27) 4981 ns/iter (± 37) 0.99
version_files/gradle_write 131888 ns/iter (± 10377) 128857 ns/iter (± 12186) 1.02

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 40af923 Previous: 0b5fe57 Ratio
version_files/toml_write 180493 ns/iter (± 30183) 132036 ns/iter (± 10286) 1.37
version_files/json_write 171106 ns/iter (± 24211) 130851 ns/iter (± 8755) 1.31

This comment was automatically generated by workflow using github-action-benchmark.

@BryanFRD
BryanFRD merged commit ecfe209 into main May 21, 2026
36 of 37 checks passed
@BryanFRD
BryanFRD deleted the feat/gix-full-migration branch May 21, 2026 06:36
BryanFRD added a commit that referenced this pull request May 21, 2026
Removes the git2 dependency entirely. The cli feature now uses gix for
all local repository operations (tag enumeration, commit walks, tree
diffs, object lookups) and shells out to the user's installed git CLI
for network operations (push, fetch, ls-remote) and write operations
(commit, tag, branch creation, checkout, reset). This is the same
hybrid strategy cargo uses.

Why shell out for network and writes:
- gix-protocol exists but its API surface is genuinely complex and the
  blocking-network-client feature pulls reqwest + a large transport
  stack. We were already shelling out for push tags (#459) and the
  credential-helper path requires shell-out anyway.
- Writes through gix (create_commit, create_tag, create_branch) require
  hand-rolling index manipulation, tree writing, and ref edits. git's
  porcelain handles all the edge cases for free.
- Diff via gix needs the blob-diff feature which pulls in gix-diff,
  gix-filter, gix-traverse - heavy for our use case (just list paths
  that changed). git diff-tree / git ls-tree is a one-liner.

What gix is used for (the perf-sensitive read paths):
- collect_all_tags, find_last_tag, find_highest_semver_tag, TagIndex
  (the hot path for monorepo tag scanning)
- get_commits_since_oid (revwalk)
- open_repo, get_repo_root, resolve_current_branch
- find_object, find_commit, find_tree for tag-to-commit resolution
- repo.references().tags() for tag iteration

Auth path:
- For shell-out calls (git push/fetch/ls-remote): inline credential
  helper via -c credential.helper config (no token in argv, no token
  in URL).
- FERRFLOW_TOKEN / GITHUB_TOKEN / GITLAB_TOKEN env vars unchanged.

Tests + benches:
- Shell-out helpers in lib.rs::test_utils and main.rs::test_utils
  (git init/add/commit/tag) replace the git2-based fixture functions.
- benches/ferrflow_benchmarks.rs uses the same shell-out fixture
  pattern; bench measurements stay comparable.
- The fetch_and_rebase and reset_branch_to_remote integration tests
  (which set up two repos + a bare remote) were rewritten with
  shell-out — same scenarios, same assertions.
- 512 lib tests pass, 614 bin tests pass, cargo clippy -D warnings clean.

Cargo.lock no longer contains git2, libgit2-sys, or openssl-src.

Also fixes two long-standing Publish workflow failures:
- ferrflow-wasm: wasm-pack's bundled wasm-opt fails on the new compiler
  output. Disable it via [package.metadata.wasm-pack.profile.release].
- npm scope rename: @ferrflow/* -> @ferrlabs/ferrflow-* (platforms),
  ferrflow -> @ferrlabs/ferrflow (wrapper), and @ferrflow/wasm ->
  @ferrlabs/ferrflow-wasm. The old @ferrflow user scope wasn't writable
  by the bot token; the @FerrLabs org scope is.

The breaking-change marker (!) is dropped: v5.0.0 already shipped via
#486 (the auth refactor) which is the SemVer-breaking piece of this
work for downstream embedders of ferrflow::git::auth. This follow-up
is a pure refactor with no public API change.
BryanFRD added a commit that referenced this pull request May 21, 2026
Removes the git2 dependency entirely. The cli feature now uses gix for
all local repository operations (tag enumeration, commit walks, tree
diffs, object lookups) and shells out to the user's installed git CLI
for network operations (push, fetch, ls-remote) and write operations
(commit, tag, branch creation, checkout, reset). This is the same
hybrid strategy cargo uses.

Why shell out for network and writes:
- gix-protocol exists but its API surface is genuinely complex and the
  blocking-network-client feature pulls reqwest + a large transport
  stack. We were already shelling out for push tags (#459) and the
  credential-helper path requires shell-out anyway.
- Writes through gix (create_commit, create_tag, create_branch) require
  hand-rolling index manipulation, tree writing, and ref edits. git's
  porcelain handles all the edge cases for free.
- Diff via gix needs the blob-diff feature which pulls in gix-diff,
  gix-filter, gix-traverse - heavy for our use case (just list paths
  that changed). git diff-tree / git ls-tree is a one-liner.

What gix is used for (the perf-sensitive read paths):
- collect_all_tags, find_last_tag, find_highest_semver_tag, TagIndex
  (the hot path for monorepo tag scanning)
- get_commits_since_oid (revwalk)
- open_repo, get_repo_root, resolve_current_branch
- find_object, find_commit, find_tree for tag-to-commit resolution
- repo.references().tags() for tag iteration

Auth path:
- For shell-out calls (git push/fetch/ls-remote): inline credential
  helper via -c credential.helper config (no token in argv, no token
  in URL).
- FERRFLOW_TOKEN / GITHUB_TOKEN / GITLAB_TOKEN env vars unchanged.

Tests + benches:
- Shell-out helpers in lib.rs::test_utils and main.rs::test_utils
  (git init/add/commit/tag) replace the git2-based fixture functions.
- benches/ferrflow_benchmarks.rs uses the same shell-out fixture
  pattern; bench measurements stay comparable.
- The fetch_and_rebase and reset_branch_to_remote integration tests
  (which set up two repos + a bare remote) were rewritten with
  shell-out — same scenarios, same assertions.
- 512 lib tests pass, 614 bin tests pass, cargo clippy -D warnings clean.

Cargo.lock no longer contains git2, libgit2-sys, or openssl-src.

Also fixes two long-standing Publish workflow failures:
- ferrflow-wasm: wasm-pack's bundled wasm-opt fails on the new compiler
  output. Disable it via [package.metadata.wasm-pack.profile.release].
- npm scope rename: @ferrflow/* -> @ferrlabs/ferrflow-* (platforms),
  ferrflow -> @ferrlabs/ferrflow (wrapper), and @ferrflow/wasm ->
  @ferrlabs/ferrflow-wasm. The old @ferrflow user scope wasn't writable
  by the bot token; the @FerrLabs org scope is.

The breaking-change marker (!) is dropped: v5.0.0 already shipped via
#486 (the auth refactor) which is the SemVer-breaking piece of this
work for downstream embedders of ferrflow::git::auth. This follow-up
is a pure refactor with no public API change.
BryanFRD added a commit that referenced this pull request May 21, 2026
Removes the git2 dependency entirely. The cli feature now uses gix for
all local repository operations (tag enumeration, commit walks, tree
diffs, object lookups) and shells out to the user's installed git CLI
for network operations (push, fetch, ls-remote) and write operations
(commit, tag, branch creation, checkout, reset). This is the same
hybrid strategy cargo uses.

Why shell out for network and writes:
- gix-protocol exists but its API surface is genuinely complex and the
  blocking-network-client feature pulls reqwest + a large transport
  stack. We were already shelling out for push tags (#459) and the
  credential-helper path requires shell-out anyway.
- Writes through gix (create_commit, create_tag, create_branch) require
  hand-rolling index manipulation, tree writing, and ref edits. git's
  porcelain handles all the edge cases for free.
- Diff via gix needs the blob-diff feature which pulls in gix-diff,
  gix-filter, gix-traverse - heavy for our use case (just list paths
  that changed). git diff-tree / git ls-tree is a one-liner.

What gix is used for (the perf-sensitive read paths):
- collect_all_tags, find_last_tag, find_highest_semver_tag, TagIndex
  (the hot path for monorepo tag scanning)
- get_commits_since_oid (revwalk)
- open_repo, get_repo_root, resolve_current_branch
- find_object, find_commit, find_tree for tag-to-commit resolution
- repo.references().tags() for tag iteration

Auth path:
- For shell-out calls (git push/fetch/ls-remote): inline credential
  helper via -c credential.helper config (no token in argv, no token
  in URL).
- FERRFLOW_TOKEN / GITHUB_TOKEN / GITLAB_TOKEN env vars unchanged.

Tests + benches:
- Shell-out helpers in lib.rs::test_utils and main.rs::test_utils
  (git init/add/commit/tag) replace the git2-based fixture functions.
- benches/ferrflow_benchmarks.rs uses the same shell-out fixture
  pattern; bench measurements stay comparable.
- The fetch_and_rebase and reset_branch_to_remote integration tests
  (which set up two repos + a bare remote) were rewritten with
  shell-out — same scenarios, same assertions.
- 512 lib tests pass, 614 bin tests pass, cargo clippy -D warnings clean.

Cargo.lock no longer contains git2, libgit2-sys, or openssl-src.

Also fixes two long-standing Publish workflow failures:
- ferrflow-wasm: wasm-pack's bundled wasm-opt fails on the new compiler
  output. Disable it via [package.metadata.wasm-pack.profile.release].
- npm scope rename: @ferrflow/* -> @ferrlabs/ferrflow-* (platforms),
  ferrflow -> @ferrlabs/ferrflow (wrapper), and @ferrflow/wasm ->
  @ferrlabs/ferrflow-wasm. The old @ferrflow user scope wasn't writable
  by the bot token; the @FerrLabs org scope is.

The breaking-change marker (!) is dropped: v5.0.0 already shipped via
#486 (the auth refactor) which is the SemVer-breaking piece of this
work for downstream embedders of ferrflow::git::auth. This follow-up
is a pure refactor with no public API change.
BryanFRD added a commit that referenced this pull request May 21, 2026
Removes the git2 dependency entirely. The cli feature now uses gix for
all local repository operations (tag enumeration, commit walks, tree
diffs, object lookups) and shells out to the user's installed git CLI
for network operations (push, fetch, ls-remote) and write operations
(commit, tag, branch creation, checkout, reset). This is the same
hybrid strategy cargo uses.

Why shell out for network and writes:
- gix-protocol exists but its API surface is genuinely complex and the
  blocking-network-client feature pulls reqwest + a large transport
  stack. We were already shelling out for push tags (#459) and the
  credential-helper path requires shell-out anyway.
- Writes through gix (create_commit, create_tag, create_branch) require
  hand-rolling index manipulation, tree writing, and ref edits. git's
  porcelain handles all the edge cases for free.
- Diff via gix needs the blob-diff feature which pulls in gix-diff,
  gix-filter, gix-traverse - heavy for our use case (just list paths
  that changed). git diff-tree / git ls-tree is a one-liner.

What gix is used for (the perf-sensitive read paths):
- collect_all_tags, find_last_tag, find_highest_semver_tag, TagIndex
  (the hot path for monorepo tag scanning)
- get_commits_since_oid (revwalk)
- open_repo, get_repo_root, resolve_current_branch
- find_object, find_commit, find_tree for tag-to-commit resolution
- repo.references().tags() for tag iteration

Auth path:
- For shell-out calls (git push/fetch/ls-remote): inline credential
  helper via -c credential.helper config (no token in argv, no token
  in URL).
- FERRFLOW_TOKEN / GITHUB_TOKEN / GITLAB_TOKEN env vars unchanged.

Tests + benches:
- Shell-out helpers in lib.rs::test_utils and main.rs::test_utils
  (git init/add/commit/tag) replace the git2-based fixture functions.
- benches/ferrflow_benchmarks.rs uses the same shell-out fixture
  pattern; bench measurements stay comparable.
- The fetch_and_rebase and reset_branch_to_remote integration tests
  (which set up two repos + a bare remote) were rewritten with
  shell-out — same scenarios, same assertions.
- 512 lib tests pass, 614 bin tests pass, cargo clippy -D warnings clean.

Cargo.lock no longer contains git2, libgit2-sys, or openssl-src.

Also fixes two long-standing Publish workflow failures:
- ferrflow-wasm: wasm-pack's bundled wasm-opt fails on the new compiler
  output. Disable it via [package.metadata.wasm-pack.profile.release].
- npm scope rename: @ferrflow/* -> @ferrlabs/ferrflow-* (platforms),
  ferrflow -> @ferrlabs/ferrflow (wrapper), and @ferrflow/wasm ->
  @ferrlabs/ferrflow-wasm. The old @ferrflow user scope wasn't writable
  by the bot token; the @FerrLabs org scope is.

The breaking-change marker (!) is dropped: v5.0.0 already shipped via
#486 (the auth refactor) which is the SemVer-breaking piece of this
work for downstream embedders of ferrflow::git::auth. This follow-up
is a pure refactor with no public API change.
BryanFRD added a commit that referenced this pull request May 21, 2026
Removes the git2 dependency entirely. The cli feature now uses gix for
all local repository operations (tag enumeration, commit walks, tree
diffs, object lookups) and shells out to the user's installed git CLI
for network operations (push, fetch, ls-remote) and write operations
(commit, tag, branch creation, checkout, reset). This is the same
hybrid strategy cargo uses.

Why shell out for network and writes:
- gix-protocol exists but its API surface is genuinely complex and the
  blocking-network-client feature pulls reqwest + a large transport
  stack. We were already shelling out for push tags (#459) and the
  credential-helper path requires shell-out anyway.
- Writes through gix (create_commit, create_tag, create_branch) require
  hand-rolling index manipulation, tree writing, and ref edits. git's
  porcelain handles all the edge cases for free.
- Diff via gix needs the blob-diff feature which pulls in gix-diff,
  gix-filter, gix-traverse - heavy for our use case (just list paths
  that changed). git diff-tree / git ls-tree is a one-liner.

What gix is used for (the perf-sensitive read paths):
- collect_all_tags, find_last_tag, find_highest_semver_tag, TagIndex
  (the hot path for monorepo tag scanning)
- get_commits_since_oid (revwalk)
- open_repo, get_repo_root, resolve_current_branch
- find_object, find_commit, find_tree for tag-to-commit resolution
- repo.references().tags() for tag iteration

Auth path:
- For shell-out calls (git push/fetch/ls-remote): inline credential
  helper via -c credential.helper config (no token in argv, no token
  in URL).
- FERRFLOW_TOKEN / GITHUB_TOKEN / GITLAB_TOKEN env vars unchanged.

Tests + benches:
- Shell-out helpers in lib.rs::test_utils and main.rs::test_utils
  (git init/add/commit/tag) replace the git2-based fixture functions.
- benches/ferrflow_benchmarks.rs uses the same shell-out fixture
  pattern; bench measurements stay comparable.
- The fetch_and_rebase and reset_branch_to_remote integration tests
  (which set up two repos + a bare remote) were rewritten with
  shell-out — same scenarios, same assertions.
- 512 lib tests pass, 614 bin tests pass, cargo clippy -D warnings clean.

Cargo.lock no longer contains git2, libgit2-sys, or openssl-src.

Also fixes two long-standing Publish workflow failures:
- ferrflow-wasm: wasm-pack's bundled wasm-opt fails on the new compiler
  output. Disable it via [package.metadata.wasm-pack.profile.release].
- npm scope rename: @ferrflow/* -> @ferrlabs/ferrflow-* (platforms),
  ferrflow -> @ferrlabs/ferrflow (wrapper), and @ferrflow/wasm ->
  @ferrlabs/ferrflow-wasm. The old @ferrflow user scope wasn't writable
  by the bot token; the @FerrLabs org scope is.

The breaking-change marker (!) is dropped: v5.0.0 already shipped via
#486 (the auth refactor) which is the SemVer-breaking piece of this
work for downstream embedders of ferrflow::git::auth. This follow-up
is a pure refactor with no public API change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(auth): stop embedding FERRFLOW_TOKEN in remote URL

2 participants