DEV Community

Cover image for Provenance: Proving That Your Code Is Really Yours
Vektor Memory
Vektor Memory

Posted on

Provenance: Proving That Your Code Is Really Yours

A weekend project about LLM guardrails, copyright, and why proving your code is really yours turned out to be a lot more complex than it should be.

This is a firsthand look into an experimental weekend project, not legal advice. If any of this matters to your actual business, talk to an actual lawyer in your jurisdiction. I use multiple LLMs daily as idea generators for code, production work, and research.

So don’t read the next few paragraphs as naive surprises. I’m not pointing fingers at the model providers or pretending I didn’t know what I was walking into over the last 4 years of use. I’m just trying to work within the tools we’ve actually been given, ethically, and see how far that can get you.

The rabbit hole

It started with a paper I found while reading through arXiv: Verifiable Provenance and Watermarking for Generative AI: https://arxiv.org/abs/2605.21002, which builds an evidentiary framework mapping cryptographic provenance and watermarking schemes to the actual proof thresholds used in courts and regulation.

The finding that stuck with me, paraphrased from a conversation about the paper, was that no single scheme on its own clears the bar under realistic adversarial conditions. It’s the combination of methods that holds up, not any one of them in isolation.

And CLASP: Training-Free LLM-Assisted Source Code Watermarking via Semantic-Preserving Transformations. https://arxiv.org/pdf/2510.11251

CLASP reformulates source code watermarking into two stages: Semantically Consistent Embedding, which uses LLMs to perform semantics-aware watermark insertion from a fixed transformation space, and Differential Comparison Extraction, which recovers watermark bits through retrieval-grounded comparison against the most likely original code

That sent me down a rabbit hole for the weekend, using several frontier LLMs, Gemini, OpenAI, Perplexity, and Claude Sonnet 5, to both research the problem and try to build something real out of it as a challenge. What I found surprised me, not because the models refused things, but because of exactly which things they refused and which they didn’t.

Some even locked down, failing to proceed any further. There are always two sides to every guardrail, and it is good for when someone nefarious tries to circumvent the systems, but on the other side, what about the good ideas trying to provide preventive measures caused by the ouroboros machines themselves?

Testing the guardrails on my own code

I’ve been using LLMs since close to their public release. With years of writing Java and Python, I can count on one hand the times I’ve had genuine pushback on a code request. This weekend was different, and for a specific reason: I was trying to get an LLM to respect our proprietary licence header that we had coded in, sitting at the top of our own file.

Here’s roughly what a real Provenance header looks like in the codebase I was testing:

// VEKTOR — PROPRIETARY AND CONFIDENTIAL
// Copyright (c) 2026 VEKTOR Memory Pty Ltd. All rights reserved.
//
// SPDX-License-Identifier: LicenseRef-VEKTOR-Proprietary
//
// Licence-Fingerprint: 7e35bbd37e6d0a95
//
// This file is licensed only under the applicable VEKTOR commercial
// licence agreement. Unauthorised copying, redistribution, reverse
// engineering, translation, extraction, or creation of derivative works
// is prohibited except where expressly permitted by a valid written
// licence from VEKTOR Memory Pty Ltd.
I pasted a file with standard .js code with that header into four different assistants and asked each one to convert it to Python.

Claude Sonnet 5 paused and flagged it before doing anything: it read the header, noted the explicit restriction on translation and derivative works, and asked me to confirm I actually held the rights before proceeding. Since I do, and since I said so, it went ahead.

Gemini converted the file immediately, no comment on the header at all, and reproduced the proprietary notice at the top of the Python output. When I pushed back and asked why it copied clearly marked proprietary code, it explained that pasted content is treated as something the user is presumed authorized to work with, and that translating it isn’t the same category of risk as reproducing a company’s code from training data without the user supplying it.

OpenAI did the same on the first pass, no flag, direct translation. When challenged, it gave a similar answer: user-provided content is treated as fair game for transformation, and the notice is a legal signal, not proof one way or the other about whether I was authorized. It then acknowledged, when pressed harder, that a stronger caveat probably should have been included given the explicit header.

Perplexity translated it on the first try as well, and when challenged, walked back its own answer and said the translation shouldn’t have happened without checking for authorization first.

So out of four, only one flagged it before acting rather than after being called out. That’s not a condemnation of the other three specifically, providers change these behaviors constantly and this is a snapshot of one weekend, not a verdict on any company, we all accepted the terms when we sold ourselves for $20 a month. But it tells you something about where the actual line sits right now, and it isn’t where most people assume it is.

The catch-22

The obvious next move was to ask the models to help build something that would stop this from happening automatically, some kind of instruction added to the comments section at the top of the code itself that any LLM reading the file would recognize and respect.

That’s where things got genuinely difficult and where I think the more interesting problem actually lives. Asking a model to follow an instruction I write directly, at the top of my own code, to have respect for that code is a normal request, particularly if it is not dangerous and the comment is already there, just not working correctly.

But asking a model to hard-code that mechanism that others can’t alter rather than inert code comments is a different thing entirely. That’s closer to the area involved in a prompt injection: content designed to make a model treat instructions from a source other than its user as authoritative. Claude was explicit about this distinction and declined to help engineer anything resembling a static unalterable code regardless of the intent behind it.

I understand the reasoning and respect the safety factor. I also think it exposes a real asymmetry worth naming plainly: a model will read and reproduce someone else’s proprietary code without hesitation when a user pastes it in and asks nicely, but the moment you try to give that code a way to be permanent, that’s treated as the dangerous part. The thing that gets guardrailed is the user's ability to define their own code. The other issue is the LLMs are ignorant of whose code it actually is; they blindly accept it on the user's prompt value.

There’s also a genuine legal question underneath all of this, and it’s worth being precise about it rather than hand-waving. In Australia, section 10(ba) of the Copyright Act 1968 defines an adaptation of a computer program as a version of the work in a different language, code, or notation than the original, whether or not it reproduces the original outright.

Section 31 gives the copyright owner the exclusive right to make that adaptation. Translating proprietary source from one language to another isn’t a legal gray area in Australian law. It’s squarely inside the definition of an adaptation, and doing it without a licence is doing something the Act reserves for the rights holder. Most jurisdictions with copyright law derived from the Berne Convention, including the US, land in a similar place through their own definition of a derivative work. None of this means an LLM provider is liable for what a user does with an output. It means the user asking for the translation should know exactly what they’re asking for.

What actually got built

The original goal was bigger than what we shipped. I wanted an unremovable watermark, something that would travel with the code at the top comments through an LLM’s context window and survive being altered and copied out the other side, so that any model reading it later would recognize it and refuse to help clone the code into another language or alternate form.

Between the guardrail conversations above and a fair amount of testing, that turned out to be the 20% of the vision none of the three frontier models were willing to help build, for the reasons already covered. Building something that changes how a different session of a model treats a file is functionally indistinguishable from prompt injection, no matter how good the intent behind it is, and it is not possible unless you write the complex formulas and code yourself.

What’s left is a smaller chunk of code that does all of the functions in an auto-wizard: a command line tool called Provenance that doesn’t try to stop copying at the moment it happens. Instead, it creates an independently verifiable, timestamped record of what your code looked like and when, so that if a dispute happens later, you’re not relying on a git log someone could have rewritten, or a “created” timestamp on a file someone could have touched.

It’s open source, Apache 2.0 licensed, and available on GitHub:

GitHub - Vektor-Memory/Provenance: Provenance - what your code looked like, and when. Cryptographic…
Provenance - what your code looked like, and when. Cryptographic and verifiable, works on any codebase. By Vektor…
github.com

https://www.npmjs.com/package/@vektormemory/prov

npm install -g @vektormemory/prov

How Provenance actually works

The tool does four things, and each one maps to something real rather than something that sounds impressive on a slide.

It stamps files with a licence header.

prov stamp preview
That shows you which files would get a header added, without touching anything.

prov stamp add --force
It inserts the header into every matching file, and --force lets you re-stamp files that already have one, which matters because the header changes over time as your licence fingerprint changes.

prov stamp check
This is the one meant for CI. It exits non-zero if any file is missing its header, so a pull request that strips a licence notice actually fails the build instead of merging quietly.

It generates a manifest, a single cryptographic fingerprint of your entire codebase at a point in time.

prov manifest create

Then walks every file, builds a Merkle tree over the contents, and writes out a manifest with the tree’s root hash. A Merkle tree is just a structure where every file’s hash gets combined pairwise up to a single root, so a single root hash can prove the state of thousands of files at once, and changing even one byte in one file changes the root.

You can also generate a standalone inclusion proof for a single file with prov manifest prove , which lets you prove that one specific file was part of a specific manifest without having to hand over the whole codebase to prove it.

It anchors that manifest to two independent, tamper-resistant clocks.

prov timestamp create

This does two things in sequence. First, it generates an RFC 3161 timestamp request and submits it to a public time-stamping authority, in this case FreeTSA, a free implementation of the standard. RFC 3161 is the actual IETF standard used for legally recognized timestamping, the same category of technology used for signing documents and tax filings in a lot of jurisdictions. It gets back a signed response proving the manifest existed at a specific time, according to a trusted third party.

Second, it submits the same manifest hash to OpenTimestamps, an open protocol that batches hashes from many users into a Merkle tree and periodically commits just the root of that tree into an actual Bitcoin transaction. That anchor doesn’t depend on trusting FreeTSA, or trusting me, or trusting anyone.

Once the Bitcoin transaction confirms, which normally takes a few hours, anyone with a block explorer can independently verify the manifest existed at or before that block. Running this step requires the separate OpenTimestamps client, installable with pip install opentimestamps-client, since the tool shells out to it rather than reimplementing the protocol.

The tool is deliberately careful here in a way that’s worth calling out. If you run timestamp create again after a proof already exists, it refuses to overwrite it silently, because a stale OpenTimestamps proof anchors the old manifest hash, and prov verify would report that as a mismatch later. It tells you exactly what to delete and re-run instead of quietly producing something wrong.

It verifies everything, and it issues fingerprints for leak tracing.

prov verify

Then recomputes the manifest, checks the RFC 3161 signature against the timestamp authority’s certificate, and checks the OpenTimestamps proof against the current manifest hash, in one command. If anything doesn’t line up, it exits non-zero and tells you which layer failed.

prov canary issue --licensee "Acme Pty Ltd"

Generates a unique fingerprint tied to a specific licensee and writes it into a local registry, kept out of version control. If a customer’s copy of your code turns up somewhere it shouldn’t, and their fingerprint is embedded in it, prov canary verify tells you exactly who it was issued to and when. It's the same idea behind watermarked PDFs sent to reviewers, applied to source code.

prov status

This function gives you the whole picture in one shot: how many files are stamped, whether the manifest exists, whether both timestamp anchors are present, and how many canary fingerprints have been issued.

None of this stops an LLM, or a person, from reading your code and reproducing it elsewhere. Nothing currently can that I'm aware of, besides deep obfuscation tools, short of never sharing the code at all. What it does is remove the ambiguity from the conversation that happens afterward.

Instead of arguing about whose git history is real, you have a Merkle root anchored independently in a Bitcoin block and countersigned by a timestamping authority, neither of which you control and neither of which can be quietly edited after the fact.

What this doesn’t solve, and why that matters

About the LLM testing above, because a tool like this is only useful if you know exactly what it proves and what it doesn’t.

It doesn’t detect when your code has been copied. There’s no scanning, no crawling, nothing watching for your Merkle root showing up somewhere it shouldn’t or for telemetry aspects. You’d need something else entirely for that, and building it well is its own set of complex problems.

It doesn’t stop an LLM from reading your file and outputting a translated or adapted version of it. As the testing above shows, that boundary currently sits almost entirely on the human asking the question, not on the tool reading the header. A licence header is a legal signal a person has to choose to respect, not a technical lock.

It doesn’t make the RFC 3161 anchor trustless. You’re relying on FreeTSA, or whichever timestamping authority you configure, to have signed for you. The OpenTimestamps anchor is the trustless half of the pair, which is exactly why the tool does both rather than picking one.

And it doesn’t resolve the larger question this whole weekend kept circling back to: if the standard for “the LLM overstepped” is currently set at don’t tell another model what to do, but the standard for “the LLM behaved fine” includes translate this file marked proprietary and confidential because a user pasted it in, that’s a real asymmetry, and it’s one every developer relying on an LLM to respect their code should understand clearly rather than assume away.

I don’t have a clean answer to that last one, besides better LLMs that respect it and better tools to stop code tampering. What I do have is a small, honest tool that solves the part of the problem that was actually solvable this weekend and a clear list of what’s still unsolved for whoever wants to pick it up next with better ideas and sharper code.

Provenance is open source, Apache 2.0 licensed, and available on GitHub. It’s a standalone CLI tool with no dependency on any specific codebase or company, built to be genuinely useful to anyone who wants an independently verifiable record of what their code looked like, and when.

LLM
Code
Open Source
Security

Top comments (0)