git-tag-guardian 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.guardian.yml +38 -0
- package/README.md +280 -0
- package/bin/guardian.js +408 -0
- package/package.json +44 -0
- package/src/checks/actions-pinning.js +136 -0
- package/src/checks/postinstall-hooks.js +170 -0
- package/src/checks/sha-pinning.js +172 -0
- package/src/checks/tag-integrity.js +229 -0
- package/src/checks/tarball-integrity.js +117 -0
- package/src/index.js +172 -0
- package/src/utils/baseline.js +85 -0
- package/src/utils/config.js +147 -0
- package/src/utils/logger.js +28 -0
package/.guardian.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# git-tag-guardian — конфигурация
|
|
2
|
+
# Положите этот файл в корень проекта
|
|
3
|
+
|
|
4
|
+
checks:
|
|
5
|
+
# Проверка: git-зависимости должны ссылаться на SHA, не на теги
|
|
6
|
+
shaPinning: true
|
|
7
|
+
|
|
8
|
+
# Проверка: обнаружение lifecycle-скриптов (postinstall и др.)
|
|
9
|
+
postinstallHooks: true
|
|
10
|
+
|
|
11
|
+
# Проверка: верификация SHA тегов через GitHub API
|
|
12
|
+
# Требует GITHUB_TOKEN
|
|
13
|
+
tagIntegrity: true
|
|
14
|
+
|
|
15
|
+
# Проверка: .tar.gz хеш должен совпадать с baseline
|
|
16
|
+
tarballIntegrity: true
|
|
17
|
+
|
|
18
|
+
# Проверка: GitHub Actions должны использовать SHA pinning
|
|
19
|
+
actionsPinning: true
|
|
20
|
+
|
|
21
|
+
# Пакеты, которым разрешены lifecycle-скрипты
|
|
22
|
+
# (не будут генерировать alert)
|
|
23
|
+
allowedLifecyclePackages: []
|
|
24
|
+
# - electron
|
|
25
|
+
# - sharp
|
|
26
|
+
# - better-sqlite3
|
|
27
|
+
# - @parcel/watcher
|
|
28
|
+
|
|
29
|
+
# Файл baseline (содержит проверенные SHA)
|
|
30
|
+
baselineFile: ".guardian-baseline.json"
|
|
31
|
+
|
|
32
|
+
# GitHub Actions: owner/action паттерны, которым доверяем
|
|
33
|
+
# (будут иметь severity: low вместо high)
|
|
34
|
+
trustedActions:
|
|
35
|
+
- "actions/*"
|
|
36
|
+
|
|
37
|
+
# Минимальный severity для CI fail
|
|
38
|
+
failOnSeverity: high
|
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# git-tag-guardian
|
|
2
|
+
|
|
3
|
+
**Zero-dependency supply chain defense for Node.js and Bun projects.**
|
|
4
|
+
|
|
5
|
+
Detects and blocks Git Tag Rewrite attacks, postinstall backdoors, SHA drift in lockfiles, tarball tampering, and unpinned GitHub Actions — before they compromise your CI/CD or production.
|
|
6
|
+
|
|
7
|
+
> Built by **CanisterWorm / TeamPCP** as a practical countermeasure to real-world tag rewrite exploitation techniques.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
Git tags are **mutable**. Anyone with push access to a repository can silently repoint a tag to a completely different commit — including one from a fork that contains a backdoor. npm, GitHub Actions, and Dockerfiles that reference dependencies by tag will then pull the malicious code without any warning.
|
|
14
|
+
|
|
15
|
+
**Attack chain:**
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
1. Attacker forks your dependency
|
|
19
|
+
2. Adds malicious code in the fork (postinstall exfiltration, modified source, etc.)
|
|
20
|
+
3. Pushes to fork — commit lands in shared GitHub Object Store
|
|
21
|
+
4. Rewrites the tag in upstream: PATCH /git/refs/tags/v1.0.0 { sha: "malicious", force: true }
|
|
22
|
+
5. GitHub regenerates .tar.gz automatically
|
|
23
|
+
6. Next `npm install` pulls compromised code
|
|
24
|
+
7. postinstall runs silently — steals env vars, SSH keys, tokens
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This tool detects **every stage** of that chain.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# As a dev dependency
|
|
35
|
+
npm install --save-dev git-tag-guardian
|
|
36
|
+
|
|
37
|
+
# Or run directly
|
|
38
|
+
npx git-tag-guardian audit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No external dependencies. Works on Node.js >= 18 and Bun.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 1. Full audit of current project
|
|
49
|
+
npx git-tag-guardian audit
|
|
50
|
+
|
|
51
|
+
# 2. Create a baseline (records current SHA hashes)
|
|
52
|
+
npx git-tag-guardian baseline
|
|
53
|
+
|
|
54
|
+
# 3. Install git hooks (pre-commit + post-merge)
|
|
55
|
+
npx git-tag-guardian hooks-install
|
|
56
|
+
|
|
57
|
+
# 4. Run in CI (exits 1 on critical/high findings)
|
|
58
|
+
npx git-tag-guardian audit --ci
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 5 Security Checks
|
|
64
|
+
|
|
65
|
+
### 1. SHA Pinning
|
|
66
|
+
|
|
67
|
+
Detects git dependencies in `package.json` that reference tags or branches instead of full 40-character SHA hashes. Tags can be rewritten; SHA hashes cannot.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
[HIGH] Dependency "logger" references tag "v1.0.0" instead of SHA
|
|
71
|
+
fix: Replace with full SHA commit hash
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Postinstall Hook Detection
|
|
75
|
+
|
|
76
|
+
Scans every package in `node_modules/` for lifecycle scripts (`preinstall`, `install`, `postinstall`, `prepare`, etc.) and analyzes their content for 15+ suspicious patterns:
|
|
77
|
+
|
|
78
|
+
| Pattern | Risk |
|
|
79
|
+
|---------|------|
|
|
80
|
+
| `curl` / `wget` / `fetch()` to external URLs | Data exfiltration |
|
|
81
|
+
| `process.env` access | Credential theft |
|
|
82
|
+
| `.ssh/` access | SSH key theft |
|
|
83
|
+
| `.bashrc` / `.zshrc` modification | Persistent backdoor |
|
|
84
|
+
| `.npmrc` modification | Registry hijacking |
|
|
85
|
+
| `child_process` / `exec` / `eval` | Arbitrary code execution |
|
|
86
|
+
| `Buffer.from(..., 'base64')` | Obfuscation |
|
|
87
|
+
| `os.userInfo()` / `os.hostname()` | Reconnaissance |
|
|
88
|
+
| `dns.resolve` / `dns.lookup` | DNS exfiltration |
|
|
89
|
+
|
|
90
|
+
Packages with 3+ suspicious patterns are flagged as **CRITICAL**.
|
|
91
|
+
|
|
92
|
+
### 3. Tag Integrity (GitHub API)
|
|
93
|
+
|
|
94
|
+
Queries the GitHub API to verify that tag SHA matches the baseline. Detects tag rewrite attacks in real-time. Also checks whether the commit belongs to the default branch or is a **fork commit** (strong indicator of attack).
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
[CRIT] TAG REWRITE DETECTED: owner/repo#v1.0.0 SHA changed!
|
|
98
|
+
baseline SHA: 1fadba8...
|
|
99
|
+
remote SHA: 16e4324... (from fork!)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Requires `GITHUB_TOKEN` environment variable.
|
|
103
|
+
|
|
104
|
+
### 4. Tarball Integrity
|
|
105
|
+
|
|
106
|
+
Downloads `.tar.gz` archives from GitHub for tag-based dependencies and compares SHA256 hashes against the baseline. GitHub regenerates tarballs dynamically after tag rewrite — same URL, different content.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
[CRIT] TARBALL CHANGED: "logger" .tar.gz hash changed!
|
|
110
|
+
baseline: 475308ac...
|
|
111
|
+
current: 4204c3e4...
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 5. GitHub Actions Pinning
|
|
115
|
+
|
|
116
|
+
Scans `.github/workflows/*.yml` for `uses:` directives that reference actions by tag instead of SHA. An action tag rewrite gives attackers access to `GITHUB_TOKEN`, all secrets, and build artifacts.
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
[HIGH] Action "third-party/action@v2" uses tag instead of SHA!
|
|
120
|
+
fix: uses: third-party/action@<40-char-sha> # v2
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Baseline System
|
|
126
|
+
|
|
127
|
+
The baseline records known-good SHA hashes for dependencies, tags, and tarballs. Subsequent audits compare current state against the baseline to detect drift.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Create initial baseline
|
|
131
|
+
npx git-tag-guardian baseline
|
|
132
|
+
|
|
133
|
+
# Commit it
|
|
134
|
+
git add .guardian-baseline.json
|
|
135
|
+
git commit -m "Add supply chain baseline"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The baseline file (`.guardian-baseline.json`) should be committed to your repository so every developer and CI runner uses the same reference.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## GitHub Actions Workflow
|
|
143
|
+
|
|
144
|
+
A ready-to-use CI workflow is included at `.github/workflows/supply-chain-guard.yml`:
|
|
145
|
+
|
|
146
|
+
- Runs on every PR that touches `package.json` / `package-lock.json`
|
|
147
|
+
- Runs on push to `main` / `master`
|
|
148
|
+
- Scheduled daily cron for tag rewrite detection between commits
|
|
149
|
+
- Posts findings as PR comments on failure
|
|
150
|
+
- All actions in the workflow itself are SHA-pinned
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# Key: install with --ignore-scripts to prevent postinstall execution
|
|
154
|
+
- run: npm ci --ignore-scripts
|
|
155
|
+
- run: npx git-tag-guardian audit --ci
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Git Hooks
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npx git-tag-guardian hooks-install
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Installs two hooks:
|
|
167
|
+
|
|
168
|
+
- **pre-commit** — blocks commits that introduce SHA drift or new lifecycle hooks
|
|
169
|
+
- **post-merge** — automatically checks dependencies after `git pull`
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Configuration
|
|
174
|
+
|
|
175
|
+
Create `.guardian.yml` in your project root (optional — sensible defaults are used otherwise):
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
checks:
|
|
179
|
+
shaPinning: true
|
|
180
|
+
postinstallHooks: true
|
|
181
|
+
tagIntegrity: true # requires GITHUB_TOKEN
|
|
182
|
+
tarballIntegrity: true
|
|
183
|
+
actionsPinning: true
|
|
184
|
+
|
|
185
|
+
# Packages allowed to have lifecycle scripts
|
|
186
|
+
allowedLifecyclePackages:
|
|
187
|
+
- electron
|
|
188
|
+
- sharp
|
|
189
|
+
- better-sqlite3
|
|
190
|
+
|
|
191
|
+
# Actions owners trusted (severity: low instead of high)
|
|
192
|
+
trustedActions:
|
|
193
|
+
- actions/*
|
|
194
|
+
|
|
195
|
+
# Baseline file name
|
|
196
|
+
baselineFile: .guardian-baseline.json
|
|
197
|
+
|
|
198
|
+
# Minimum severity to fail CI
|
|
199
|
+
failOnSeverity: high
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## CLI Reference
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
COMMANDS:
|
|
208
|
+
audit Full project audit (all 5 checks)
|
|
209
|
+
baseline Create/update SHA baseline
|
|
210
|
+
verify Quick check (SHA drift + postinstall only)
|
|
211
|
+
hooks-install Install git hooks (pre-commit, post-merge)
|
|
212
|
+
help Show help
|
|
213
|
+
|
|
214
|
+
FLAGS:
|
|
215
|
+
--ci CI mode (exit 1 on critical/high findings)
|
|
216
|
+
--cwd <path> Project root (default: cwd)
|
|
217
|
+
--token <tok> GitHub PAT for API checks (or GITHUB_TOKEN env)
|
|
218
|
+
--output <file> Save report as JSON
|
|
219
|
+
--no-color Disable colors
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Integration with AI Coding Agents
|
|
225
|
+
|
|
226
|
+
This project ships companion skills for Claude Code and OpenCode that automatically run supply chain checks when the agent installs npm packages:
|
|
227
|
+
|
|
228
|
+
- **[claude-supply-chain-skill](https://github.com/47-ron/claude-supply-chain-skill)** — Claude Code / Anthropic CLI skill
|
|
229
|
+
- **[opencode-supply-chain-skill](https://github.com/47-ron/opencode-supply-chain-skill)** — OpenCode agent skill
|
|
230
|
+
|
|
231
|
+
These skills hook into the agent's `npm install` / `bun install` workflow and block installation if critical findings are detected.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## How It Works (Technical)
|
|
236
|
+
|
|
237
|
+
### Git Tag Rewrite Attack
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
Upstream repo: owner/logger
|
|
241
|
+
refs/tags/v1.0.0 → commit abc123 (clean)
|
|
242
|
+
|
|
243
|
+
Attacker fork: attacker/logger
|
|
244
|
+
branch: malicious → commit xyz789 (backdoor)
|
|
245
|
+
|
|
246
|
+
API call (with push access):
|
|
247
|
+
PATCH /repos/owner/logger/git/refs/tags/v1.0.0
|
|
248
|
+
{ "sha": "xyz789", "force": true }
|
|
249
|
+
|
|
250
|
+
Result:
|
|
251
|
+
refs/tags/v1.0.0 → commit xyz789 (backdoor!)
|
|
252
|
+
.tar.gz regenerated with malicious content
|
|
253
|
+
npm install pulls backdoor automatically
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Why SHA Pinning Stops It
|
|
257
|
+
|
|
258
|
+
SHA-256 commit hashes are cryptographically unique. A rewritten tag points to a different SHA — but if your `package-lock.json` or baseline records the original SHA, the drift is immediately detectable.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Severity Levels
|
|
263
|
+
|
|
264
|
+
| Severity | Meaning | CI Impact |
|
|
265
|
+
|----------|---------|-----------|
|
|
266
|
+
| `critical` | Active attack indicator (SHA drift, tag rewrite, dangerous hooks) | Fail |
|
|
267
|
+
| `high` | Missing protection (tag-based deps, unpinned actions) | Fail |
|
|
268
|
+
| `medium` | Informational risk (short SHA, missing baseline) | Pass |
|
|
269
|
+
| `low` | Best practice suggestion | Pass |
|
|
270
|
+
| `info` | Informational only | Pass |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
*CanisterWorm / TeamPCP — Practical security tooling for the Node.js ecosystem.*
|