
How to Investigate a GitHub Repo's Real History
Step-by-step guide to reconstructing the real history of a tampered GitHub repo using data the attacker can't forge: raw commit objects (git cat-file), GitHub's repository activity API and audit log, before/after SHA ancestry checks, and npm publish timestamps. Worked through a PolinRider case (nestjsx/nest-access-control) where a forged commit was force-pushed to master plus 22 branches in 38 seconds, with a malicious .vscode/tasks.json runOn folderOpen trigger. It also covers safe handling of untrusted clones and how to tell victim accounts from attacker accounts.
What it is
A six-step investigative methodology for proving that a GitHub repo's commit history was forged, built around a real case: the npm package nest-access-control (repo nestjsx/nest-access-control) was infected with PolinRider malware (a DPRK campaign) that force-pushed a tampered commit disguised as a routine chore: add package-lock.json dependency bump.
How it works
The core insight: commit metadata (author/committer name, email, date) is plain text stored inside the commit object and can be freely forged by a script, but several other data sources cannot be altered after the fact:
- GitHub's push activity log (
GET /repos/{owner}/{repo}/activity) records every push with GitHub's own server timestamp, actor, and before/after SHA — independent of anything in the commit itself. - Before/after SHA ancestry: on a legitimate push,
beforeis an ancestor ofafter; on a force-push that rewrites history, it isn't. Check withgit merge-base --is-ancestor <before> <after>(exit code 1 = rewritten). - npm registry publish timestamps (
curl https://registry.npmjs.org/<package> | jq '.time') are set server-side at actualnpm publishtime and can't be edited retroactively — useful for scoping whether a supply-chain compromise actually reached published packages, not just the repo. - Commit signing: in the case study, the real commit was GPG-signed and GitHub-verified (
"verified": true); the forged replacement copied the author's name/email but came back"verified": false, "reason": "unsigned"since the attacker didn't have the private signing key. - A tell-tale pattern: the same forged tree pushed to many branches within seconds (in the case study, 16 branches in a 38-second window) indicates an automated script, not normal development.
flowchart TD
A[Commit metadata: author/committer date] -->|forgeable, local clock| X[Cannot trust alone]
B[GitHub push activity log] -->|server-side timestamp| V[Independent verification]
C[npm/PyPI publish time object] -->|server-side timestamp| V
D[GPG signature verification] -->|requires private key| V
E[Surviving old commit: forks, CI logs, PR timeline] -->|untouched by force-push| V
V --> F[Reassemble UTC timeline: who, when, which ref]What to do
git cat-file -p <commit-sha>— compare author vs committer timestamp/timezone for mismatches.- Pull GitHub's activity feed; if you have org admin access, the audit log (
git.pushevents, retained 7 days) gives authenticated-actor ground truth. git merge-base --is-ancestor <before-sha> <after-sha>; echo $?— confirm history rewrite.- Cross-check against npm/PyPI publish timestamps (not Go/PHP registries, which mirror the source repo).
- Check for surviving pre-tampering commits via collaborator forks, CI logs, PR force-push events, or
git fetch origin <old-sha>by SHA. - Lay out a four-column timeline (timestamp UTC, actor, ref, event) to spot the pattern.
Safety guidance for handling the untrusted clone: work on a disposable, network-restricted VM, never npm install/build/run the code, don't open it in an editor with auto-run tasks enabled (malicious .vscode/tasks.json with runOn: folderOpen was the actual injection vector here), check .gitmodules before recursive clone, don't follow the repo's own setup instructions, and use scoped/revocable credentials only. Keep git patched — git clone --recursive has a known exploit (CVE-2025-48384).
To distinguish a compromised victim account from an actual attacker: look for a genuine prior track record versus padding/forks only, a sharp break from normal pattern (versus uniformly suspicious activity), account age, follower/following ratios, and breadth of targeting across unrelated repos. Default assumption should be victim, not attacker — the page recommends private maintainer notification over public accusation.
Caveats
The page notes commit-hash mismatches can theoretically expose tampering (git fetch shows "forced update", git status shows divergence), but this only works if someone already holds a pre-rewrite copy and notices the warning — most people run git pull --rebase or git reset --hard origin/main and silently pull in the malicious version anyway. Repos with no other clone holders (personal projects) have no fallback detection at all.
Organisations named here
- commit backdating
- force-push
- git history manipulation
- temp_auto_push.bat
CVEs named here
- CVE-2025-48384
Technologies named here
- git cat-file -p
- npm package nest-access-control
- VS Code
- GitHub organization audit log
- git merge-base --is-ancestor
- git
- git fetch origin <sha>
- GitHub accounts michaelyali and shekohex
- git diff, git status
- npm registry metadata for independent publish timestamps
- curl
- jq
- Postman / Insomnia
- GitHub commit verification
- AI coding assistant
- nestjsx/nest-access-control
- nest-access-control
- GitHub REST API - List repository activities endpoint
- Windows
- npm registry API
- GitHub
- npm
Techniques named here
- Git history manipulation
- Decoy binary font files padding the injected payload
- Victim vs attacker account assessment
- Typosquat npm packages paired with faked repo history
- Git commit metadata forgery
- Force-push history rewrite
- Mass branch poisoning
- Auto-executing IDE task injection
- Malicious decoy file padding
- Malicious postinstall/preinstall scripts
- Malicious .gitmodules submodule pointers
- Onboarding instruction hijack
- Identity/account spoofing via commit metadata
- Credential harvesting via environment variables
Threat actors named here
- DPRK IT workers
- PolinRider
- DPRK IT worker scheme
- DPRK
Related in Blue Team & Detection
AI-Coded Malware | Analyzing Vibe-Coded AD Enumeration | Huntress
