push-sentinel 0.1.1 → 0.1.2
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/README.md +47 -54
- package/package.json +1 -1
- package/src/scan.js +16 -4
package/README.md
CHANGED
|
@@ -1,88 +1,79 @@
|
|
|
1
1
|
# push-sentinel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Catches secrets in your git commits before they leave your machine.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
You've seen the stories. Someone pushes an AWS key to a public repo. Bots scrape GitHub in seconds. The bill arrives the next morning: $8,000.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
push-sentinel sits in your `pre-push` hook and warns you before that happens.
|
|
8
8
|
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
npx push-sentinel install
|
|
13
9
|
```
|
|
10
|
+
$ git push
|
|
14
11
|
|
|
15
|
-
This writes a `pre-push` hook to `.git/hooks/`. Any existing hook is preserved as `pre-push.local` and called automatically after the scan.
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
### Automatic (after install)
|
|
20
|
-
|
|
21
|
-
The hook runs on every `git push`. No action required.
|
|
22
|
-
|
|
23
|
-
```
|
|
24
12
|
[push-sentinel] ⚠ Potential secrets found:
|
|
25
13
|
|
|
26
14
|
[HIGH] src/config.ts:12
|
|
27
15
|
AKIAIO...
|
|
28
|
-
→ Risk: Full access to AWS resources. Attacker can create/delete
|
|
16
|
+
→ Risk: Full access to AWS resources. Attacker can create/delete
|
|
17
|
+
instances, incur charges, or exfiltrate data.
|
|
29
18
|
→ To ignore this line: push-sentinel ignore src/config.ts:12
|
|
30
19
|
|
|
31
20
|
Push continues. Double-check before sharing.
|
|
32
21
|
```
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
## Install
|
|
35
24
|
|
|
36
25
|
```sh
|
|
37
|
-
npx push-sentinel
|
|
26
|
+
npx push-sentinel install
|
|
38
27
|
```
|
|
39
28
|
|
|
40
|
-
|
|
29
|
+
That's it. Runs automatically on every `git push` from now on.
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
## What it detects
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
| Pattern | Severity |
|
|
34
|
+
|---------|----------|
|
|
35
|
+
| Private Key (RSA, EC, OPENSSH, DSA, PKCS#8) | 🔴 HIGH |
|
|
36
|
+
| AWS Access Key (`AKIA...`) | 🔴 HIGH |
|
|
37
|
+
| AWS Secret Key (entropy-based) | 🔴 HIGH |
|
|
38
|
+
| GitHub Token (`ghp_`, `github_pat_`) | 🔴 HIGH |
|
|
39
|
+
| Anthropic API Key (`sk-ant-...`) | 🟡 MEDIUM |
|
|
40
|
+
| OpenAI API Key (`sk-...`) | 🟡 MEDIUM |
|
|
41
|
+
| Generic API Key (variable name + high entropy) | 🟢 LOW |
|
|
42
|
+
| `.env` file committed | 🟡 MEDIUM |
|
|
47
43
|
|
|
48
|
-
##
|
|
44
|
+
## False positive? Ignore it in one command
|
|
49
45
|
|
|
50
46
|
```sh
|
|
51
|
-
#
|
|
52
|
-
push-sentinel ignore
|
|
47
|
+
push-sentinel ignore src/config.ts:12 # ignore a specific line
|
|
48
|
+
push-sentinel ignore --pattern OPENAI_API_KEY # ignore a pattern everywhere
|
|
49
|
+
push-sentinel ignore --list # see all ignore rules
|
|
50
|
+
```
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
push-sentinel ignore --pattern OPENAI_API_KEY
|
|
52
|
+
Rules are saved to `.push-sentinel-ignore` in your repo root.
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
push-sentinel ignore --list
|
|
54
|
+
## Why warning-only by default?
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
push-sentinel ignore --remove OPENAI_API_KEY
|
|
62
|
-
```
|
|
56
|
+
Blocking pushes creates friction. Friction leads to `--no-verify`. A warning at push time is early enough to catch real accidents — and you'll actually leave it installed.
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
Want hard blocking for HIGH findings? Add `--block-on-high`:
|
|
65
59
|
|
|
66
|
-
|
|
60
|
+
```sh
|
|
61
|
+
# edit .git/hooks/pre-push, change the scan line to:
|
|
62
|
+
npx push-sentinel scan --local-sha "$local_sha" --remote-sha "$remote_sha" --block-on-high
|
|
63
|
+
```
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|---------|----------|
|
|
70
|
-
| Private Key (RSA, EC, OPENSSH, DSA, PKCS#8) | HIGH |
|
|
71
|
-
| AWS Access Key (`AKIA...`) | HIGH |
|
|
72
|
-
| AWS Secret Key (entropy-based) | HIGH |
|
|
73
|
-
| GitHub Token (`ghp_`, `github_pat_`) | HIGH |
|
|
74
|
-
| Anthropic API Key (`sk-ant-...`) | MEDIUM |
|
|
75
|
-
| OpenAI API Key (`sk-...`) | MEDIUM |
|
|
76
|
-
| Generic API Key (variable name + high entropy) | LOW |
|
|
77
|
-
| `.env` file committed | MEDIUM |
|
|
65
|
+
## Manual scan
|
|
78
66
|
|
|
79
|
-
|
|
67
|
+
```sh
|
|
68
|
+
npx push-sentinel scan
|
|
69
|
+
```
|
|
80
70
|
|
|
81
|
-
|
|
71
|
+
Manual scan checks, in order:
|
|
82
72
|
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
73
|
+
- commits not yet pushed to your upstream
|
|
74
|
+
- staged changes
|
|
75
|
+
- unstaged working tree changes
|
|
76
|
+
- the last commit as a final fallback
|
|
86
77
|
|
|
87
78
|
## Uninstall
|
|
88
79
|
|
|
@@ -90,9 +81,11 @@ push-sentinel warns — it does not block — because:
|
|
|
90
81
|
npx push-sentinel uninstall
|
|
91
82
|
```
|
|
92
83
|
|
|
93
|
-
|
|
84
|
+
Your original `pre-push` hook is restored automatically.
|
|
94
85
|
|
|
95
|
-
##
|
|
86
|
+
## Details
|
|
96
87
|
|
|
88
|
+
- Scans only the commits being pushed — not your entire history
|
|
89
|
+
- Zero dependencies (Node.js stdlib only)
|
|
97
90
|
- Node.js >= 16
|
|
98
|
-
-
|
|
91
|
+
- Existing `pre-push` hooks are preserved and still run
|
package/package.json
CHANGED
package/src/scan.js
CHANGED
|
@@ -119,14 +119,24 @@ function runGit(args) {
|
|
|
119
119
|
return result.status === 0 ? (result.stdout || '') : null;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function runGitNonEmpty(args) {
|
|
123
|
+
const out = runGit(args);
|
|
124
|
+
return out && out.trim() ? out : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
122
127
|
function getPushDiff(localSha, remoteSha) {
|
|
123
128
|
const args = getDiffArgs(localSha, remoteSha);
|
|
124
129
|
if (args) {
|
|
125
130
|
const out = runGit(args);
|
|
126
131
|
if (out !== null) return out;
|
|
127
132
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
133
|
+
// Manual scan: inspect unpushed commits first, then staged changes, then working tree,
|
|
134
|
+
// and finally fall back to the last commit if none of those produce a diff.
|
|
135
|
+
return runGitNonEmpty(['log', '@{u}..HEAD', '-p'])
|
|
136
|
+
?? runGitNonEmpty(['diff', '--cached'])
|
|
137
|
+
?? runGitNonEmpty(['diff'])
|
|
138
|
+
?? runGit(['log', '-1', '-p', 'HEAD'])
|
|
139
|
+
?? '';
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
function getPushedFileList(localSha, remoteSha) {
|
|
@@ -135,8 +145,10 @@ function getPushedFileList(localSha, remoteSha) {
|
|
|
135
145
|
const out = runGit(args);
|
|
136
146
|
if (out !== null) return out.split('\n').map((f) => f.trim()).filter(Boolean);
|
|
137
147
|
}
|
|
138
|
-
//
|
|
139
|
-
const out =
|
|
148
|
+
// Manual scan: include files from unpushed commits, staged changes, and working tree.
|
|
149
|
+
const out = runGitNonEmpty(['diff', '--name-only', '@{u}..HEAD'])
|
|
150
|
+
?? runGitNonEmpty(['diff', '--name-only', '--cached'])
|
|
151
|
+
?? runGitNonEmpty(['diff', '--name-only'])
|
|
140
152
|
?? runGit(['diff', '--name-only', 'HEAD~1..HEAD'])
|
|
141
153
|
?? '';
|
|
142
154
|
return out.split('\n').map((f) => f.trim()).filter(Boolean);
|