This post is going back to the basics in forensic investigations.
Not because the basics are simple.
Usually it’s the opposite.
Once an investigation starts producing interesting results, the boring rules become surprisingly easy to bend.
You find the same pattern twice.
Then some metadata lines up.
Then the timing looks weird.
Then another account appears.
Then another repository.
Pretty soon you have enough correlated evidence that your brain really wants there to be one clean explanation for all of it.
That is usually the point where the most important question becomes:
What does the evidence actually prove?
I recently finished a GitHub investigation into what appeared to be a larger persona-fabrication or automated repository-production ecosystem.
The case ended up producing some pretty strong evidence: repeated repository structures, shared content templates, recurring metadata, mechanically staged activity, cross-account relationships, and indicators that the behavior extended well beyond one isolated account.
But one of the strongest attribution hypotheses still failed its locked threshold.
That wasn’t the investigation breaking.
That was the methodology working.
A quick source note before going further: unless I explicitly call something an illustrative example, the measurements and examples below come from the case record. I’m keeping later external research separate from the closed investigation rather than quietly mixing the two.
Start with what you observed
One of the easiest mistakes in an investigation is jumping straight from an observation to an explanation.
Something looks automated, so we call it automation.
Two accounts behave similarly, so we start treating them as one operator.
The same identity appears in metadata, so we start talking about control.
That skips several evidentiary steps.
In this case, accounts including ACCOUNT_A and ACCOUNT_B showed behavior that justified continuing the investigation.
But the first useful statement wasn’t:
These accounts are controlled by the same person.
It was closer to:
These accounts show repeatable technical and behavioral relationships worth testing.
That sounds less exciting.
It is also much harder to accidentally overstate.
One repo can already tell you a lot
A useful example from the case was REPO_01.
Its activity contained a mechanically staged commit sequence compressed into roughly twenty seconds.
That kind of timing is interesting.
It can suggest automation, templating, scripted repository construction, or some other highly structured process.
But even something that obvious has limits.
A twenty-second construction sequence can support:
highly structured repository creation
It might support:
likely automation or scripted workflow
when combined with other evidence.
It does not, by itself, tell you:
who operated the account
That difference sounds trivial until you are twenty pivots deep into an investigation.
Repetition becomes much stronger when it survives removal
One of the content-analysis results eventually found:
26 shared nontrivial templates
That was interesting, but there was an obvious question:
What if one really distinctive string was carrying the whole result?
The strongest normalized template was:
update readme with clean structured content
So the sensitivity test removed it and reran the calculation.
The result:
26 shared templates
↓
remove strongest template
↓
25 remain
That is a legitimately useful result.
The relationship did not disappear when the strongest individual feature was removed.
But there is another basic distinction hiding here.
Robustness isn’t specificity
These are different questions.
ROBUSTNESS
Does the result survive removal of one feature?
versus:
SPECIFICITY
How rare is the remaining pattern outside the subjects being tested?
The 26 → 25 result gives us evidence about robustness.
It tells us the result is not just one memorable phrase appearing twice.
It does not automatically tell us those remaining 25 templates uniquely identify one person or one pair of accounts.
For example, imagine a repository generator always produces:
Add package.json
Add .gitignore
Add tests
Update README
That example is illustrative, not case evidence.
If fifty accounts are generated by the same tooling, those strings might be great indicators of the tooling and terrible indicators of individual identity.
That’s a distinction I think matters far beyond this particular investigation:
A strong campaign signature is not automatically a strong identity signature.
Metadata is evidence. It isn’t magic.
Another important part of the investigation involved Co-authored-by trailers.
Git commit messages can contain metadata like:
Co-authored-by: Name
In this investigation, recurring trailer identities became useful pivots.
Later expansion work around TRAILER_ID, for example, was explicitly built around searching for and preserving occurrences of:
Co-authored-by: TRAILER_ID
That is valuable evidence.
But it is still commit-message metadata.
Someone can write a trailer into a commit message.
So this:
same trailer
+
same repository behavior
+
same content patterns
+
same timing characteristics
+
other independent relationships
can become strong linkage evidence.
This:
same Co-authored-by trailer
same human
does not follow.
Again, that looks almost too obvious when written out.
It gets much harder to remember once six or seven independent-looking signals are all pointing in roughly the same direction.
Scale can change what the evidence means
Another part of this investigation expanded beyond one or two repositories.
The TRAILER_ID expansion branch exposed a set of 22 mechanically named security-oriented repositories, with names in the register of things like:
SentinelHub
HexaShield
ZeroTrust
...
The scale matters.
If you find the same artifact in two repositories, you might initially think in terms of a pairwise connection.
If you later find related operational characteristics distributed across a much larger repository set, your interpretation may need to change.
Instead of:
account A <----> account B
you may be looking at something closer to:
┌── account
├── account
shared process ──┼── account
or tooling ├── account
└── account
That is a very different claim.
And it is not a weaker finding.
Sometimes the most interesting outcome of trying to prove a pairwise relationship is discovering that you were measuring a larger system.
Let the threshold fail
This might be my favorite part of the whole case.
One of the frozen hypotheses, T-H3, required a qualifying owner bridge to meet an owner-span threshold of:
required owner span = 10
The actual qualifying result was:
owner_span = 2
So:
2 < 10
T-H3 = FAIL
That is the answer.
Not:
but 2 looks suspicious
Not:
the rest of the evidence is strong enough
Not:
maybe 10 was too strict anyway
The threshold was frozen before interpreting the result.
If the rule says ten and the evidence gives you two, the hypothesis fails.
Then you can investigate why it failed.
Maybe the hypothesis was wrong.
Maybe the evidence is describing a different phenomenon.
Maybe the bridge is genuinely pair-specific.
Maybe a later version of the methodology should test a different population.
Those are all legitimate questions.
What you cannot do is move the finish line because the evidence got close to the conclusion you wanted.
Negative results are evidence too
Suppose an investigation has hypotheses that roughly test:
T-H0: ordinary / unrelated activity
T-H1: meaningful shared-tooling or content relationship
T-H2: structured distribution infrastructure
T-H3: stronger ownership/control condition
The actual result does not have to be a neat stack of successes.
In this case, the more accurate summary was closer to:
T-H1:
high confidence / well-supported
T-H2:
moderate confidence
plausible, but not independently identified
T-H3:
FAIL
That is a useful result.
In fact, it is much better than collapsing everything into:
the accounts are connected
because “connected” can mean almost anything.
Shared content?
Shared tooling?
Shared automation?
Same campaign?
Same distribution infrastructure?
Same operator?
Same real-world human?
Those are different propositions and should have different evidence requirements.
The point of having separate hypotheses is that they are allowed to land differently.
A result like “plausible but not independently identified” is not something that needs to be massaged into “supported.”
It is the result.
If every hypothesis magically succeeds, I would be more suspicious of the methodology than impressed by the investigation.
The investigation even caught its own mechanism-level error
There was another smaller but important example.
After the analysis, the explanatory record around T-H3 needed correction.
The final verdict itself did not change.
The underlying result was still:
owner_span = 2
required = 10
T-H3 = FAIL
But the recorded explanation of why the rule failed was incomplete.
The direct-bridge logic had effectively been described or implemented too narrowly, around same-commit or same-repository relationships, while the frozen rule also allowed a shared owner namespace to satisfy that bridge condition.
So the bridge mechanism needed correcting.
The threshold result did not.
That distinction became AMENDMENT-001.
I like this example because it captures another basic forensic rule:
Correct the mechanism without rewriting the outcome.
The important sequence was roughly:
original interpretation
↓
mechanism-level discrepancy identified
↓
frozen rule checked
↓
bridge interpretation corrected
↓
owner_span still = 2
↓
T-H3 still FAIL
The amendment made the explanation more accurate without pretending the earlier artifact had never existed.
That matters.
If an explanatory or implementation-level field changes, don’t replace the old record and erase the history.
Preserve the lineage.
Conceptually:
original artifact
↓
hash
↓
identified mechanism-level defect
↓
documented amendment
↓
new artifact
↓
new hash
The old artifact tells you what existed.
The amendment tells you why it changed.
The new artifact tells you what replaced it.
A changed hash isn’t automatically suspicious
This came up directly in the case.
An earlier summary.json had one SHA-256 value.
A later version had another.
At first glance:
old_hash != new_hash
looks like an integrity problem.
It might be.
But hashes answer a very narrow question:
Are these byte streams identical?
If the contents legitimately changed, the hashes should differ.
So you need provenance alongside integrity.
Something conceptually like:
Reproducibility Record v1
summary.json
sha256 = ...
status = superseded, retained
Then:
AMENDMENT-001
old_summary_sha256 = ...
new_summary_sha256 = ...
reason:
mechanism-level explanatory correction
method_changed = false
outcome_changed = false
Then:
Reproducibility Record v2
summary.json
sha256 = ...
Now the mismatch itself isn’t mysterious.
You can explain exactly where it came from.
A pretty useful way of remembering this is:
A hash tells you whether the bytes changed. Provenance tells you why.
Freeze the corpus too
Another thing GitHub investigations make painfully obvious is that your evidence source is alive.
Accounts keep working.
Repositories change.
New repositories appear.
Search indexes update.
Pull requests change.
New matching artifacts can show up after you’ve already finished analyzing the original dataset.
So:
CASE COMPLETE
is not enough.
What you really want is something closer to:
case_status: complete
evidence_cutoff:
corpus_snapshot:
protocol_version:
analysis_version:
result_as_of_cutoff:
Then if you discover something tomorrow, you haven’t magically changed yesterday’s result.
You have new evidence.
That becomes an addendum or a new investigative phase.
This distinction turned out to matter in this case because later GitHub research exposed additional accounts and workflow patterns beyond the frozen investigation.
Those later observations may be valuable.
But they should not quietly become retroactive evidence inside the original case.
That is exactly how provenance gets muddy.
Observation is not attribution
The simplest mental model I’ve taken away from all of this is:
OBSERVATION
↓
CORRELATION
↓
LINKAGE
↓
OPERATIONAL INFERENCE
↓
ATTRIBUTION
Every arrow needs evidence.
You might start with:
OBSERVATION
Multiple repositories contain recurring artifacts.
Then:
CORRELATION
Those artifacts repeatedly occur alongside similar
repository structure and timing.
Then:
LINKAGE
Additional metadata or cross-repository relationships
connect parts of the activity.
Then:
OPERATIONAL INFERENCE
The behavior is consistent with shared tooling,
automation, or coordinated repository production.
But:
ATTRIBUTION
These accounts belong to the same real-world person.
is another evidentiary jump.
You don’t get that arrow for free.
What I would actually claim
The evidence gave me enough confidence to say that the investigated GitHub activity contained repeated, highly structured operational signatures consistent with shared tooling, automation, templated repository production, or a broader coordinated workflow.
The shared-tooling relationship was well supported.
A more structured distribution-infrastructure explanation remained plausible, but I could not independently identify it strongly enough to treat it as established.
The investigation also produced meaningful relationships between specific owner namespaces and recurring metadata identities.
What I would not turn that into without stronger evidence is:
account A == account B == one human operator
The investigation’s strongest ownership/control hypothesis did not satisfy its frozen threshold.
That boundary belongs in the result.
The boring forensic rules matter
After spending a lot of time on this case, the things I keep coming back to aren’t particularly exotic.
Preserve the original evidence.
Hash it.
Preserve changed versions too.
Document why they changed.
Freeze the methodology before interpreting the final result.
Do not move thresholds afterward.
Keep commit metadata separate from authenticated identity evidence.
Separate robustness from specificity.
Separate pair-level evidence from campaign-level evidence.
Keep later research separate from the frozen case corpus.
Clearly label hypothetical examples.
Timestamp the evidence boundary.
Preserve failed hypotheses.
Preserve partially supported hypotheses too.
And don’t be afraid of an answer that is narrower than the one you expected.
Because probably the most important rule is still:
State the strongest conclusion the evidence can survive — not the strongest conclusion you can make the evidence resemble.
That’s about as basic as forensic discipline gets.
And the more interesting the investigation becomes, the more important those basics are.
Top comments (0)