← All Blog articles

Proving Source Code Ownership With Git and Hashes

Git history is not proof that you wrote or own your code. A commit hash is good at one thing: showing that the history you’re looking at hasn’t been altered.

What Git Can and Can’t Prove About Who Wrote Your Code

Git history is not proof that you wrote or own your code. A commit hash is good at one thing: showing that the history you’re looking at hasn’t been altered. The name, email address and date attached to a commit are things the committer typed or configured, and anyone can change them. What holds up better is pairing a clean Git history with an independent timestamp of a SHA-256 hash of your repository snapshot. That combination can show that this exact content existed no later than a certain moment. It still doesn’t settle ownership, because ownership comes from copyright law and contracts, not from version control.

The rest of this article covers what each piece does, where it stops, and how to build an evidence trail that holds up under questioning.

Git’s hashes aren’t what most people think they are

Every object in Git (file contents, directory trees, commits) is named by a hash of its contents. A commit object contains a pointer to the top-level tree, pointers to its parent commits, author and committer lines, and the message. The commit ID is the hash of all that text.

The consequence is neat. Change one character in a file from three years ago and every commit after it gets a new ID. History is chained, so quiet edits to the past are visible to anyone holding the original IDs.

But look at what that guarantees. It guarantees consistency: this chain of content hangs together. It says nothing about when the chain was built, or who built it. Someone can create a hundred commits in an afternoon and they’ll chain together just as neatly as a hundred commits made over five years.

One more detail worth knowing: most repositories still use SHA-1 for object names. Git moved to a hardened SHA-1 implementation in version 2.13 after the SHAttered collision demonstration, and the project has a documented transition design toward SHA-256, which a repository can already opt into, though older Git versions can’t read such repositories. For evidence purposes, this matters. If you want a fingerprint of your work that rests on a stronger function than SHA-1, you’ll want to compute it yourself rather than rely on the commit ID alone.

The commit date is a note you wrote to yourself

Run git log --format=fuller on any repository and you’ll see two dates per commit: an author date and a commit date. Both come from the committer’s machine, and both can be overridden. A backdated commit takes one line:

GIT_AUTHOR_DATE="2019-03-01T10:00:00" GIT_COMMITTER_DATE="2019-03-01T10:00:00" git commit -m "initial import"

The --date option does the same for the author date. Author names and emails work the same way: they come from git config, and nothing checks them against a real person. Security write-ups on Git spoofing routinely show commits “from” famous developers that those developers never made.

Hosting platforms add their own signals, such as when a push was received, and those are worth having. But they are records held by a company you’re trusting, and you generally can’t check them independently of that company.

None of this makes Git a bad tool. It’s a version control system, and nobody designed it to be a notary.

What a signature adds, and what it leaves open

Signing commits and tags (with GPG, SSH keys or X.509 certificates) is a real step up. A valid signature shows that someone holding a particular private key signed that commit, and that the commit hasn’t changed since.

Two gaps remain. First, a signature ties a commit to a key, not to a human being. You still need a trustworthy story for how that key became yours: a published fingerprint, a key listed on your website years ago, a hosting profile. Second, the time recorded in a signature is also generated on the signer’s machine. A signed commit tells you who vouches for the content. It doesn’t independently tell you when.

Project-level conventions, such as the Signed-off-by line used under the Developer Certificate of Origin, are statements by contributors about their right to submit code. They’re useful for governance, but they are assertions, not verification.

Ownership is a legal question, and Git answers a different one

It helps to separate four things people tend to lump together:

Integrity: has this content changed? Hashes answer this well.

Existence at a time: did this content exist by a given date? An independent timestamp answers this.

Authorship: who actually wrote it? Evidence here is circumstantial: drafts, history, key control, witnesses, working files.

Ownership: who holds the rights? This comes from law and agreements.

On the last point, take the United States as an example. The Copyright Office states that copyright exists from the moment a work is created and fixed in a tangible form, and that registration is voluntary in general, though you need to register a US work before bringing an infringement lawsuit in federal court. Source code is a literary work for these purposes. So in the US your copyright doesn’t depend on any proof you create afterwards. What proof does is help you win the factual argument about who wrote what, and when.

Ownership can also sit with someone other than the person who typed the code. Work done as an employee is commonly treated as “work made for hire”, in which case the employer is the owner. Contractor work depends on the contract. Rules differ by country, so if a real dispute is on the table, a lawyer in the relevant jurisdiction is the right next call. A hash timestamp doesn’t resolve any of this, and a page that suggests otherwise is overselling.

Pinning your repository to a moment in time

Here’s the part that fills the gap Git leaves: an independent record that a specific snapshot existed by a specific time. The workflow below uses ordinary Git commands plus a hash anchored to a blockchain.

Make the snapshot a single file:

A hash needs one file to fingerprint. Git can package an entire repository, history included, into one:

git bundle create myproject-2026-10-31.bundle --all

git bundle verify myproject-2026-10-31.bundle

A bundle carries every commit and ref you included, so it captures history rather than just the latest files. If you only care about a release’s contents, git archive --format=tar HEAD -o snapshot.tar produces a plain archive of the files at that commit. Hash the uncompressed tar if you can. Compression can differ between tools, and you don’t want your fingerprint to depend on a gzip version.

Fingerprint it and anchor it:

With Certelo, the SHA-256 hash is calculated locally, in your browser, from the file on your device. The bundle isn’t uploaded to create the proof. That matters for source code in particular, where the whole point may be that nobody else has seen it yet. The hash is then anchored to the Electra Protocol blockchain, which gives you a record that anyone can check against the chain: this fingerprint existed at or before the recorded blockchain time.

If you prefer to double-check the hash yourself, sha256sum myproject-2026-10-31.bundle (or shasum -a 256 on macOS) gives the same value.

Keep the file:

This is the step people skip. The hash proves nothing unless you can later produce the exact file that generated it. Store the bundle somewhere durable, keep a copy of the certificate information, and write down the Git version you used. Change one byte of the bundle and the hash no longer matches.

A hypothetical dispute, walked through

This is an invented scenario, not a real case.

Maya, a freelance developer, spends 2022 building a scheduling engine for a client who later declines to pay and walks away. In 2024 she notices a competitor’s product behaving suspiciously like her engine. Her private repository has 400 commits. The client’s lawyer says those commit dates are just text she could have edited. They’re right about that.

Had Maya bundled her repository in late 2022 and anchored its hash, she’d have a different answer. She could produce the bundle, show that its SHA-256 matches the anchored hash, and point to a blockchain record that predates the competitor’s launch. The record doesn’t show she’s the author, so she’d still bring the rest: signed commits tied to a key she’s used for years, her working files, emails. It does take away the “you could have backdated everything” argument for that snapshot, and it does so without her having revealed the code to anyone.

A lighter option: anchoring only the commit ID

You can also take the output of git rev-parse HEAD and put it in a small text file, then timestamp that file. Because a commit ID covers the whole history behind it, this does bind everything below that commit.

The trade-off is that the binding is only as strong as the hash function Git used to name the objects. A SHA-256 over a bundle rests on SHA-256 end to end. Treat the commit-ID file as a convenient extra, with the bundle as the primary evidence.

Where this still falls short

* It doesn’t show who wrote the code. If someone copies your code and timestamps it first, their timestamp is earlier than yours. A timestamp measures priority of a fingerprint, nothing more.

* It covers only what’s in the file. Code you add later needs a new snapshot. Anchoring regularly, such as at each release or milestone, builds a timeline.

* Third-party code comes along for the ride. A bundle includes dependencies and open-source code you didn’t write. The timestamp doesn’t separate your work from theirs.

* Courts decide the weight. How a court treats a blockchain record depends on the jurisdiction and the circumstances. For more on that, see our piece on the legal validity of blockchain records as court evidence. In the EU, “qualified” electronic time stamps have a specific legal status under eIDAS, and a general-purpose blockchain timestamp isn’t automatically one of them. Our explainer on trusted timestamping lays out the difference.

Checking it later

Verification is deliberately boring. Produce the original bundle, compute its SHA-256, and compare it to the anchored value using the Certelo verification page. If they match, the file you’re holding is the file that was fingerprinted at the time of the record. You can also run git bundle verify and inspect the history inside, then check the commits against your signing key. For a broader approach to preserving digital material for disputes, our guide to chain of custody for digital evidence covers record-keeping habits that apply here too.

Questions that come up

Can Git commits prove I wrote the code?

Not on their own. They show that a history is internally consistent. Author names, emails and dates are self-declared, and signatures prove control of a key rather than identity.

Can commit dates be faked?

Yes. Environment variables or the --date option set them to anything. That’s why they shouldn’t be your only evidence of timing.

Does pushing to GitHub prove ownership?

No. A hosting platform keeps records of activity on its service, which can be useful supporting evidence. But platform records aren’t an ownership register, and you rely on the platform to confirm them.

Do I need to upload my code to timestamp it?

Not with Certelo. The SHA-256 hash is computed locally and only the hash is anchored. You do need to keep the original file for later verification.

Is a blockchain timestamp the same as copyright registration?

No. Registration is an official process with a government office. A blockchain timestamp is a technical record that a fingerprint existed by a certain time. They do different jobs, and in the US, registration is what unlocks a federal infringement lawsuit.

What if I change the code after timestamping?

The old record still matches the old bundle, and only that bundle. New work needs a new snapshot and a new anchor.