Skip to main content

How Anonymous Push Works

gitGost provides strong anonymity by stripping all identifying metadata from your commits before creating pull requests. This guide explains the technical process and what gets anonymized.
gitGost provides strong anonymity features, but not perfect anonymity. See the Threat Model for details on what is and isn’t protected.

The Push Workflow

1

Client sends packfile

When you run git push gost, your Git client packages commits into a packfile using the Smart HTTP protocol:
git push gost my-branch:main
The packfile contains:
  • Commit objects (with your original author/committer info)
  • Tree objects (file structure)
  • Blob objects (file contents)
  • Parent commit references
2

gitGost receives and extracts

The server extracts the packfile from the Git protocol wrapper:
// From internal/git/receive.go
func ExtractPackfile(body []byte) ([]byte, *RefUpdate, string, error) {
    // Parse pkt-line protocol
    // Extract ref updates (old SHA → new SHA)
    // Locate PACK signature and extract packfile
    // Parse push-options (e.g., pr-hash)
}
3

Commits are unpacked

gitGost unpacks your commits into a temporary repository:
// Clone base repository
git.PlainClone(tempDir, false, &git.CloneOptions{
    URL: repoURL,
})

// Unpack received objects
cmd := exec.Command("git", "index-pack", "--stdin", "--fix-thin")
cmd.Stdin = bytes.NewReader(packfile)
4

Metadata is anonymized

Every commit is rewritten with anonymous author/committer signatures:
// From internal/git/receive.go:337-341
anonSignature := object.Signature{
    Name:  "@gitgost-anonymous",
    Email: "[email protected]",
    When:  time.Now(),
}

newCommit := &object.Commit{
    Author:       anonSignature,
    Committer:    anonSignature,
    Message:      commit.Message,  // Preserved!
    TreeHash:     commit.TreeHash,
    ParentHashes: newParents,
}
Your commit message is preserved—only identity metadata is stripped.
5

Fork is created

A fork is created under the @gitgost-anonymous account:
// From internal/github/pr.go:431-502
func ForkRepo(owner, repo string) (string, error) {
    // Check if fork exists
    // Create fork if needed
    // Return fork owner (@gitgost-anonymous)
}
6

Anonymized commits are pushed

The rewritten commits are pushed to a unique branch in the fork:
// From internal/git/push.go:22-80
func PushToGitHub(owner, repo, tempDir, forkOwner, targetBranch string) {
    branch := targetBranch
    if branch == "" {
        timestamp := time.Now().Unix()
        branch = fmt.Sprintf("gitgost-%d", timestamp)
    }
    
    // Push with force if updating existing branch
    r.Push(&git.PushOptions{
        RemoteName: "fork",
        RefSpecs:   []config.RefSpec{refSpec},
        Force:      targetBranch != "",
    })
}
7

PR is opened

A pull request is created from the fork to the original repository:
// From internal/github/pr.go:550-604
func CreatePR(owner, repo, branch, forkOwner, commitMessage string) {
    prBody := fmt.Sprintf(
        "%s\n\n---\n\n*This is an anonymous contribution made via gitGost.*",
        commitMessage,
    )
    
    data := map[string]interface{}{
        "title": "Anonymous contribution via gitGost",
        "head":  fmt.Sprintf("%s:%s", forkOwner, branch),
        "base":  "main",
        "body":  prBody,
    }
}

What Gets Anonymized

✅ Stripped Metadata

- Author: John Doe <[email protected]>
+ Author: @gitgost-anonymous <[email protected]>
Your name and email are replaced in all commits.
- Committer: John Doe <[email protected]>
+ Committer: @gitgost-anonymous <[email protected]>
Even if you amended commits, the committer is anonymized.
- Date: Thu Feb 15 14:32:18 2024 -0500
+ Date: Thu Mar 05 18:45:03 2026 +0000
Original timestamps are replaced with the server’s current time to prevent timezone-based identification.
PRs appear from @gitgost-anonymous, not your personal account. No link between your GitHub profile and the contribution.

✅ Preserved Data

Your commit messages are preserved exactly as written and used as the PR description. Write them carefully—see commit message best practices.
The actual diff (added/removed lines) is preserved. This is necessary for the PR to be useful.
The tree structure (which files were modified) is preserved.
If you push multiple commits, the commit graph structure is preserved (parent relationships), but all metadata is anonymized.

What gitGost Does NOT Anonymize

IP Address: Your IP is visible to the gitGost server. Use Tor integration to hide your IP.
Code Style: Your coding style, variable naming, and comments can be analyzed (stylometry). Avoid unique patterns if full anonymity is critical.
Timing Patterns: Push timing can correlate with your timezone or activity patterns. Use irregular timing if avoiding correlation is important.

Commit Rewriting Details

Recursive Parent Rewriting

When you push multiple commits, gitGost recursively rewrites the entire chain:
// From internal/git/receive.go:306-334
func rewriteCommit(r *git.Repository, commit *object.Commit, 
                   commitMap map[plumbing.Hash]plumbing.Hash,
                   baseCommits map[plumbing.Hash]bool) {
    // Skip commits already in the base repository
    if baseCommits[commit.Hash] {
        return commit.Hash, nil
    }
    
    // Rewrite parents first (depth-first)
    var newParents []plumbing.Hash
    for _, parentHash := range commit.ParentHashes {
        newParentHash := rewriteCommit(r, parentCommit, ...)
        newParents = append(newParents, newParentHash)
    }
    
    // Create anonymized commit with rewritten parents
    newCommit := &object.Commit{
        Author:       anonSignature,
        Committer:    anonSignature,
        Message:      commit.Message,
        TreeHash:     commit.TreeHash,
        ParentHashes: newParents,
    }
}

Base Commit Detection

gitGost only rewrites new commits (your changes), not existing commits from the target repository:
// From internal/git/receive.go:272-283
baseCommits := make(map[plumbing.Hash]bool)
originMain, err := r.Reference(plumbing.NewRemoteReferenceName("origin", "main"), true)
if err == nil {
    iter, _ := r.Log(&git.LogOptions{From: originMain.Hash()})
    iter.ForEach(func(c *object.Commit) error {
        baseCommits[c.Hash] = true  // Mark as base
        return nil
    })
}
This ensures:
  • Only your new commits are rewritten
  • Existing repository history remains unchanged
  • Parent references stay correct

Example: Before and After

Original Commit (Your Local Repository)

commit 9f3a7c8e4d2b1a0f5e6c7d8e9f0a1b2c3d4e5f6a
Author: Jane Smith <[email protected]m>
Committer: Jane Smith <[email protected]m>
Date:   Wed Mar 04 2026 15:23:45 -0800

    fix: correct API endpoint timeout
    
    The previous timeout of 5s was too short for slow networks.
    Increased to 30s with exponential backoff.

Anonymized Commit (In Fork)

commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
Author: @gitgost-anonymous <[email protected]l>
Committer: @gitgost-anonymous <[email protected]l>
Date:   Thu Mar 05 2026 23:45:12 +0000

    fix: correct API endpoint timeout
    
    The previous timeout of 5s was too short for slow networks.
    Increased to 30s with exponential backoff.
Notice:
  • Author/Committer changed to @gitgost-anonymous
  • Email changed to [email protected]
  • Timestamp changed to server time (UTC)
  • Commit SHA changed (since metadata changed)
  • Message preserved exactly

Edge Cases and Limitations

Multiple Commits

Pushing multiple commits works correctly:
git checkout -b feature
git commit -m "feat: add new API endpoint"
git commit -m "docs: update API documentation"
git commit -m "test: add endpoint tests"
git push gost feature:main
All three commits are anonymized while preserving their relationship.

Merge Commits

Merge commits are supported—both parents are rewritten:
git merge another-branch
git push gost merged-feature:main

Large Commits

Commits over 10 MB are rejected:
remote: error: commit size exceeds 10 MB limit
Solution: Split large changes into multiple smaller commits.

Binary Files

Binary files are supported, but count toward the 10 MB commit limit.

Security Considerations

Server Trust

You must trust the gitGost server not to log your IP. Use Tor if you need to eliminate this trust requirement.

Code Analysis

Advanced adversaries can analyze code style, commit patterns, and timing to potentially identify you. gitGost protects against casual identification, not targeted forensics.

GitHub Metadata

GitHub may log the IP of the @gitgost-anonymous bot account (the server’s IP), but not yours—unless you access the PR URL directly without Tor Browser.

Commit Messages

Don’t include personally identifiable information in commit messages. Avoid phrases like “I always use…” or references to your company/location.

Rate Limiting and Abuse Prevention

gitGost tracks pushes per IP address:
// From internal/http/handlers.go:733-759
func checkRateLimit(ip string) bool {
    now := time.Now()
    rateLimitMu.Lock()
    times := rateLimitStore[ip]
    
    // Keep only timestamps within the window
    valid := times[:0]
    for _, t := range times {
        if now.Sub(t) < rateLimitWindow {  // 1 hour
            valid = append(valid, t)
        }
    }
    valid = append(valid, now)
    rateLimitStore[ip] = valid
    count := len(valid)
    rateLimitMu.Unlock()
    
    return count > rateLimitMaxPRs  // 5 PRs
}
If you exceed 5 PRs/hour:
remote: Rate limit exceeded: max 5 PRs per hour per IP.
remote: Please try again later.
Use Tor integration to rotate your exit node if you need to contribute to multiple projects rapidly.

Temporary Repository Cleanup

All temporary data is deleted after the push:
// From internal/http/handlers.go:194-200
tempDir, err := utils.CreateTempDir()
if err != nil {
    return err
}
defer utils.CleanupTempDir(tempDir)  // Always cleanup
gitGost does not store:
  • Your packfile
  • Original commit metadata
  • Git history
  • Any persistent state about your push

Next Steps

Tor Integration

Hide your IP address from the gitGost server

Commit Messages

Write effective commit messages for anonymous PRs

Threat Model

Understand what gitGost protects against

API Reference

Technical protocol details

Build docs developers (and LLMs) love