compromising-position 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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -0
  3. package/bin/compromising-position +29 -0
  4. package/dist/checks/hibp-email.d.ts +7 -0
  5. package/dist/checks/hibp-email.d.ts.map +1 -0
  6. package/dist/checks/hibp-email.js +99 -0
  7. package/dist/checks/hibp-email.js.map +1 -0
  8. package/dist/checks/hibp-password.d.ts +13 -0
  9. package/dist/checks/hibp-password.d.ts.map +1 -0
  10. package/dist/checks/hibp-password.js +119 -0
  11. package/dist/checks/hibp-password.js.map +1 -0
  12. package/dist/checks/local-check.d.ts +9 -0
  13. package/dist/checks/local-check.d.ts.map +1 -0
  14. package/dist/checks/local-check.js +36 -0
  15. package/dist/checks/local-check.js.map +1 -0
  16. package/dist/checks/plugin.d.ts +29 -0
  17. package/dist/checks/plugin.d.ts.map +1 -0
  18. package/dist/checks/plugin.js +2 -0
  19. package/dist/checks/plugin.js.map +1 -0
  20. package/dist/checks/plugins/common-secrets-plugin.d.ts +3 -0
  21. package/dist/checks/plugins/common-secrets-plugin.d.ts.map +1 -0
  22. package/dist/checks/plugins/common-secrets-plugin.js +130 -0
  23. package/dist/checks/plugins/common-secrets-plugin.js.map +1 -0
  24. package/dist/checks/plugins/dehashed-plugin.d.ts +3 -0
  25. package/dist/checks/plugins/dehashed-plugin.d.ts.map +1 -0
  26. package/dist/checks/plugins/dehashed-plugin.js +86 -0
  27. package/dist/checks/plugins/dehashed-plugin.js.map +1 -0
  28. package/dist/checks/plugins/emailrep-plugin.d.ts +3 -0
  29. package/dist/checks/plugins/emailrep-plugin.d.ts.map +1 -0
  30. package/dist/checks/plugins/emailrep-plugin.js +95 -0
  31. package/dist/checks/plugins/emailrep-plugin.js.map +1 -0
  32. package/dist/checks/plugins/gitguardian-hsl-plugin.d.ts +3 -0
  33. package/dist/checks/plugins/gitguardian-hsl-plugin.d.ts.map +1 -0
  34. package/dist/checks/plugins/gitguardian-hsl-plugin.js +75 -0
  35. package/dist/checks/plugins/gitguardian-hsl-plugin.js.map +1 -0
  36. package/dist/checks/plugins/hibp-email-plugin.d.ts +3 -0
  37. package/dist/checks/plugins/hibp-email-plugin.d.ts.map +1 -0
  38. package/dist/checks/plugins/hibp-email-plugin.js +73 -0
  39. package/dist/checks/plugins/hibp-email-plugin.js.map +1 -0
  40. package/dist/checks/plugins/hibp-password-plugin.d.ts +3 -0
  41. package/dist/checks/plugins/hibp-password-plugin.d.ts.map +1 -0
  42. package/dist/checks/plugins/hibp-password-plugin.js +39 -0
  43. package/dist/checks/plugins/hibp-password-plugin.js.map +1 -0
  44. package/dist/checks/plugins/intelx-plugin.d.ts +3 -0
  45. package/dist/checks/plugins/intelx-plugin.d.ts.map +1 -0
  46. package/dist/checks/plugins/intelx-plugin.js +113 -0
  47. package/dist/checks/plugins/intelx-plugin.js.map +1 -0
  48. package/dist/checks/plugins/leakcheck-plugin.d.ts +3 -0
  49. package/dist/checks/plugins/leakcheck-plugin.d.ts.map +1 -0
  50. package/dist/checks/plugins/leakcheck-plugin.js +82 -0
  51. package/dist/checks/plugins/leakcheck-plugin.js.map +1 -0
  52. package/dist/checks/plugins/local-analysis-plugin.d.ts +3 -0
  53. package/dist/checks/plugins/local-analysis-plugin.d.ts.map +1 -0
  54. package/dist/checks/plugins/local-analysis-plugin.js +36 -0
  55. package/dist/checks/plugins/local-analysis-plugin.js.map +1 -0
  56. package/dist/checks/registry.d.ts +24 -0
  57. package/dist/checks/registry.d.ts.map +1 -0
  58. package/dist/checks/registry.js +53 -0
  59. package/dist/checks/registry.js.map +1 -0
  60. package/dist/config/config.d.ts +10 -0
  61. package/dist/config/config.d.ts.map +1 -0
  62. package/dist/config/config.js +56 -0
  63. package/dist/config/config.js.map +1 -0
  64. package/dist/core/entropy.d.ts +23 -0
  65. package/dist/core/entropy.d.ts.map +1 -0
  66. package/dist/core/entropy.js +180 -0
  67. package/dist/core/entropy.js.map +1 -0
  68. package/dist/core/fingerprint.d.ts +7 -0
  69. package/dist/core/fingerprint.d.ts.map +1 -0
  70. package/dist/core/fingerprint.js +10 -0
  71. package/dist/core/fingerprint.js.map +1 -0
  72. package/dist/core/key-identifier.d.ts +9 -0
  73. package/dist/core/key-identifier.d.ts.map +1 -0
  74. package/dist/core/key-identifier.js +310 -0
  75. package/dist/core/key-identifier.js.map +1 -0
  76. package/dist/core/sanitize.d.ts +7 -0
  77. package/dist/core/sanitize.d.ts.map +1 -0
  78. package/dist/core/sanitize.js +15 -0
  79. package/dist/core/sanitize.js.map +1 -0
  80. package/dist/core/secure-buffer.d.ts +61 -0
  81. package/dist/core/secure-buffer.d.ts.map +1 -0
  82. package/dist/core/secure-buffer.js +122 -0
  83. package/dist/core/secure-buffer.js.map +1 -0
  84. package/dist/index.d.ts +4 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +472 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/input/batch-parser.d.ts +21 -0
  89. package/dist/input/batch-parser.d.ts.map +1 -0
  90. package/dist/input/batch-parser.js +65 -0
  91. package/dist/input/batch-parser.js.map +1 -0
  92. package/dist/input/secure-prompt.d.ts +11 -0
  93. package/dist/input/secure-prompt.d.ts.map +1 -0
  94. package/dist/input/secure-prompt.js +105 -0
  95. package/dist/input/secure-prompt.js.map +1 -0
  96. package/dist/output/audit-log.d.ts +11 -0
  97. package/dist/output/audit-log.d.ts.map +1 -0
  98. package/dist/output/audit-log.js +50 -0
  99. package/dist/output/audit-log.js.map +1 -0
  100. package/dist/output/csv.d.ts +6 -0
  101. package/dist/output/csv.d.ts.map +1 -0
  102. package/dist/output/csv.js +28 -0
  103. package/dist/output/csv.js.map +1 -0
  104. package/dist/output/formatter.d.ts +12 -0
  105. package/dist/output/formatter.d.ts.map +1 -0
  106. package/dist/output/formatter.js +154 -0
  107. package/dist/output/formatter.js.map +1 -0
  108. package/dist/output/sarif.d.ts +6 -0
  109. package/dist/output/sarif.d.ts.map +1 -0
  110. package/dist/output/sarif.js +52 -0
  111. package/dist/output/sarif.js.map +1 -0
  112. package/dist/types/index.d.ts +141 -0
  113. package/dist/types/index.d.ts.map +1 -0
  114. package/dist/types/index.js +45 -0
  115. package/dist/types/index.js.map +1 -0
  116. package/dist/verification/anthropic-verifier.d.ts +3 -0
  117. package/dist/verification/anthropic-verifier.d.ts.map +1 -0
  118. package/dist/verification/anthropic-verifier.js +56 -0
  119. package/dist/verification/anthropic-verifier.js.map +1 -0
  120. package/dist/verification/aws-verifier.d.ts +14 -0
  121. package/dist/verification/aws-verifier.d.ts.map +1 -0
  122. package/dist/verification/aws-verifier.js +30 -0
  123. package/dist/verification/aws-verifier.js.map +1 -0
  124. package/dist/verification/github-verifier.d.ts +4 -0
  125. package/dist/verification/github-verifier.d.ts.map +1 -0
  126. package/dist/verification/github-verifier.js +62 -0
  127. package/dist/verification/github-verifier.js.map +1 -0
  128. package/dist/verification/openai-verifier.d.ts +4 -0
  129. package/dist/verification/openai-verifier.d.ts.map +1 -0
  130. package/dist/verification/openai-verifier.js +59 -0
  131. package/dist/verification/openai-verifier.js.map +1 -0
  132. package/dist/verification/slack-verifier.d.ts +4 -0
  133. package/dist/verification/slack-verifier.d.ts.map +1 -0
  134. package/dist/verification/slack-verifier.js +67 -0
  135. package/dist/verification/slack-verifier.js.map +1 -0
  136. package/dist/verification/verifier-registry.d.ts +13 -0
  137. package/dist/verification/verifier-registry.d.ts.map +1 -0
  138. package/dist/verification/verifier-registry.js +19 -0
  139. package/dist/verification/verifier-registry.js.map +1 -0
  140. package/dist/verification/verifier.d.ts +24 -0
  141. package/dist/verification/verifier.d.ts.map +1 -0
  142. package/dist/verification/verifier.js +2 -0
  143. package/dist/verification/verifier.js.map +1 -0
  144. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tommy Yau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,250 @@
1
+ # compromising-position
2
+
3
+ **Were your API keys compromised? Find out.**
4
+
5
+ A privacy-preserving credential exposure checker that identifies what kind of key you have, checks if it appears in breach databases, and optionally verifies if it's still active — all from a single CLI command.
6
+
7
+ Built in response to the [OpenClaw security crisis](https://www.theregister.com/2026/02/05/openclaw_skills_marketplace_leaky_security) where tens of thousands of API keys were exposed through leaky skills, malicious plugins, and publicly accessible instances. Security advisories tell you to rotate your keys — but **compromising-position** tells you what happened *before* you rotated.
8
+
9
+ > **Disclaimer:** I am not a security expert. This tool was vibe-coded in the age of personal software — built because I needed it and thought others might too. It aggregates publicly available breach-checking APIs and applies well-documented techniques (k-anonymity, SHA-256 hashing, Shannon entropy). It is not a substitute for professional security auditing, penetration testing, or incident response. Use it as one signal among many, not as your only line of defence. If you find a vulnerability, please [open an issue](https://github.com/tommyyau/compromising-position/issues).
10
+
11
+ ## The Problem
12
+
13
+ You connected your API keys to a service. That service got breached, misconfigured, or had a supply chain attack. You rotated your keys. But:
14
+
15
+ - Were your old keys sold on dark web marketplaces?
16
+ - Did someone use your Anthropic key to run up charges?
17
+ - Was your Slack token used to read company messages?
18
+ - Is your old key *still active* because you forgot to revoke it?
19
+
20
+ **No existing open-source tool answers all of these questions.**
21
+
22
+ ## What It Does
23
+
24
+ ```
25
+ echo "sk-proj-abc123..." | npx compromising-position check
26
+ ```
27
+
28
+ ```
29
+ --- Credential Exposure Report ---
30
+
31
+ Risk Level: [CRITICAL]
32
+ Summary: Identified as OpenAI — EXPOSED in 3 breach(es) — KEY IS CURRENTLY ACTIVE
33
+
34
+ Local Analysis
35
+ Provider: OpenAI (high confidence)
36
+ Entropy: 4.82 bits/char (0.81 normalized)
37
+
38
+ HIBP Password Check (k-Anonymity)
39
+ FOUND in breach data — 3 occurrence(s)
40
+
41
+ Plugin Results
42
+ Common/Weak Secret Detection: Not found in common secrets blocklist
43
+ GitGuardian HasMySecretLeaked: Found in 2 public GitHub repo(s)
44
+
45
+ Active Key Verification
46
+ KEY IS ACTIVE — rotate immediately!
47
+ ```
48
+
49
+ ### The Pipeline
50
+
51
+ 1. **Identify** — Recognizes 39 API key formats (OpenAI, Anthropic, AWS, GitHub, Stripe, Slack, and 33 more)
52
+ 2. **Analyze** — Shannon entropy, encoding detection, weak/common secret blocklist (NIST SP 800-63B compliant)
53
+ 3. **Check breach databases** — HIBP (k-anonymity), GitGuardian, EmailRep.io, DeHashed, LeakCheck, Intelligence X
54
+ 4. **Verify liveness** — Optionally calls the provider API to check if the key still works (read-only, with explicit consent)
55
+ 5. **Risk score** — Single assessment from `info` to `critical`
56
+
57
+ ## Quick Start
58
+
59
+ ```bash
60
+ # Install
61
+ npm install -g compromising-position
62
+
63
+ # Check a single key (interactive, hidden input)
64
+ compromising-position check
65
+
66
+ # Pipe a key in
67
+ echo "ghp_abc123..." | compromising-position check
68
+
69
+ # Check without network calls
70
+ echo "sk_live_abc123" | compromising-position check --offline
71
+
72
+ # Check + verify if key is still active (asks for consent)
73
+ echo "sk-proj-abc123" | compromising-position check --verify
74
+
75
+ # Check an email for breaches (requires HIBP API key)
76
+ compromising-position check-email user@example.com
77
+
78
+ # Batch check an entire .env file
79
+ compromising-position check-batch .env.old
80
+
81
+ # Output as SARIF for GitHub Advanced Security
82
+ compromising-position check-batch secrets.json --format sarif
83
+ ```
84
+
85
+ ## Privacy Model
86
+
87
+ Your secrets never leave your machine unless you explicitly opt in:
88
+
89
+ | Check | What's Sent | Where |
90
+ |-------|-------------|-------|
91
+ | Local analysis | Nothing | Local only |
92
+ | Common secrets blocklist | Nothing | Local only |
93
+ | HIBP password | 5-char SHA-1 prefix | api.pwnedpasswords.com |
94
+ | GitGuardian | SHA-256 hash | api.gitguardian.com |
95
+ | EmailRep.io | Full email | emailrep.io |
96
+ | Active verification | Full key | Provider API (opt-in) |
97
+
98
+ Run `compromising-position check --privacy` to see the full data flow summary.
99
+
100
+ ## Supported Key Formats (39)
101
+
102
+ | Provider | Prefix | Confidence |
103
+ |----------|--------|------------|
104
+ | OpenAI | `sk-proj-`, `sk-svcacct-`, `sk-` | high |
105
+ | Anthropic | `sk-ant-api03-` | high |
106
+ | AWS | `AKIA` | high |
107
+ | GitHub | `ghp_`, `github_pat_` | high |
108
+ | Stripe | `sk_live_`, `sk_test_` | high |
109
+ | Google | `AIza` | high |
110
+ | Slack | `xoxb-`, `xoxp-` | high |
111
+ | GitLab | `glpat-`, `glptt-` | high |
112
+ | npm | `npm_` | high |
113
+ | PyPI | `pypi-AgEIcHlwaS5vcmc` | high |
114
+ | Shopify | `shppa_`, `shpat_` | high |
115
+ | DigitalOcean | `dop_v1_`, `doo_v1_` | high |
116
+ | Supabase | `sbp_` | high |
117
+ | HashiCorp Vault | `hvs.` | high |
118
+ | Terraform Cloud | `atlasv1-` | high |
119
+ | PlanetScale | `pscale_tkn_` | high |
120
+ | Postman | `PMAK-` | high |
121
+ | Grafana | `glsa_` | high |
122
+ | Linear | `lin_api_` | high |
123
+ | Netlify | `nfp_` | high |
124
+ | Doppler | `dp.st.`, `dp.sa.` | high |
125
+ | Buildkite | `bkua_` | high |
126
+ | Atlassian | `ATATT3xFfGF0` | high |
127
+ | Figma | `figd_` | high |
128
+ | SendGrid | `SG.` | high |
129
+ | Twilio | `SK` | high |
130
+ | Mailgun | `key-` | high |
131
+ | Discord | token format | medium |
132
+ | Telegram | bot token format | high |
133
+ | CircleCI | `CIRCLE` | medium |
134
+ | Notion | `secret_` | medium |
135
+
136
+ ## Data Sources
137
+
138
+ ### Free (no API key needed)
139
+ - **HIBP Passwords** — k-anonymity, checks 600M+ breached passwords
140
+ - **Common secrets blocklist** — Top passwords, default credentials, placeholders (fully local)
141
+ - **EmailRep.io** — Email reputation, dark web presence (100 lookups/day free)
142
+
143
+ ### Requires API Key
144
+ - **HIBP Email** — Breaches, stealer logs, pastes ($3.50/mo)
145
+ - **GitGuardian** — Public GitHub repo secret scanning (free tier available)
146
+ - **DeHashed** — 15B+ records, deep/dark web
147
+ - **LeakCheck** — 28B+ records
148
+ - **Intelligence X** — Tor, I2P, paste sites
149
+
150
+ Configure keys via environment variables or `.env` file:
151
+
152
+ ```bash
153
+ export HIBP_API_KEY=your-key
154
+ export GITGUARDIAN_API_TOKEN=your-token
155
+ export EMAILREP_API_KEY=your-key # optional, higher rate limits
156
+ export DEHASHED_EMAIL=your@email.com
157
+ export DEHASHED_API_KEY=your-key
158
+ export LEAKCHECK_API_KEY=your-key
159
+ export INTELX_API_KEY=your-key
160
+ ```
161
+
162
+ ## Active Key Verification
163
+
164
+ The `--verify` flag checks if a key is still active by calling the provider's API:
165
+
166
+ | Provider | Endpoint | Method |
167
+ |----------|----------|--------|
168
+ | OpenAI | `/v1/models` | GET |
169
+ | Anthropic | `/v1/models` | GET |
170
+ | GitHub | `/user` | GET |
171
+ | Slack | `auth.test` | POST |
172
+ | AWS | Requires secret key — reports format only | — |
173
+
174
+ All verification is:
175
+ - **Opt-in only** — requires `--verify` flag
176
+ - **Consent-gated** — interactive prompt before each call
177
+ - **Read-only** — never makes write operations
178
+
179
+ ## CI/CD Integration
180
+
181
+ ### GitHub Actions
182
+
183
+ ```yaml
184
+ - name: Check rotated secrets
185
+ run: |
186
+ echo "${{ secrets.OLD_API_KEY }}" | npx compromising-position check --json
187
+ # Exit code 1 = exposed, 0 = clean
188
+ ```
189
+
190
+ ### Batch + SARIF
191
+
192
+ ```yaml
193
+ - name: Scan secrets file
194
+ run: npx compromising-position check-batch secrets.json --format sarif > results.sarif
195
+
196
+ - name: Upload SARIF
197
+ uses: github/codeql-action/upload-sarif@v3
198
+ with:
199
+ sarif_file: results.sarif
200
+ ```
201
+
202
+ ### Pre-commit Hook
203
+
204
+ ```bash
205
+ # .pre-commit-config.yaml
206
+ - repo: local
207
+ hooks:
208
+ - id: check-secrets
209
+ name: Check for weak secrets
210
+ entry: bash -c 'echo "$1" | npx compromising-position check --offline'
211
+ language: system
212
+ ```
213
+
214
+ ## Output Formats
215
+
216
+ | Flag | Format | Use Case |
217
+ |------|--------|----------|
218
+ | (default) | Human-readable | Terminal |
219
+ | `--json` | JSON | Scripting, pipelines |
220
+ | `--format sarif` | SARIF 2.1.0 | GitHub Advanced Security |
221
+ | `--format csv` | CSV | Spreadsheets, reporting |
222
+
223
+ ## Security Design
224
+
225
+ - **SecureBuffer** — All secrets held in zeroable Buffer wrappers, never plain strings
226
+ - **Constant-time comparison** — HIBP suffix matching uses `timingSafeEqual`
227
+ - **Memory zeroing** — Buffers zeroed on disposal, secure heap allocation via `--secure-heap`
228
+ - **No secret logging** — Audit logs contain only truncated SHA-256 fingerprints
229
+ - **Terminal injection protection** — All external data sanitized before terminal output
230
+ - **Explicit Resource Management** — Supports TC39 `using` keyword for automatic cleanup
231
+
232
+ ## Development
233
+
234
+ ```bash
235
+ git clone https://github.com/tommyyau/compromising-position.git
236
+ cd compromising-position
237
+ npm install
238
+ npm run build
239
+ npm test # 196 tests across 23 test files
240
+ ```
241
+
242
+ ## A Note on How This Was Built
243
+
244
+ This project was vibe-coded — built with AI assistance (Claude) in a single session, from idea to published repo. The architecture, code, and tests were generated collaboratively. I'm not a security researcher; I'm a developer who connected API keys to OpenClaw, watched the breach news unfold, and wanted a tool to answer the question: *"was I actually compromised?"*
245
+
246
+ If you're a security professional and spot something wrong, PRs and issues are very welcome.
247
+
248
+ ## License
249
+
250
+ MIT
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Launch with secure heap allocation to reduce risk of secrets
4
+ // lingering in process memory after buffer zeroing.
5
+ //
6
+ // If --secure-heap is not supported (older Node), fall back gracefully.
7
+
8
+ import { execFileSync } from "node:child_process";
9
+ import { fileURLToPath } from "node:url";
10
+ import { dirname, resolve } from "node:path";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const entry = resolve(__dirname, "..", "dist", "index.js");
14
+
15
+ try {
16
+ execFileSync(
17
+ process.execPath,
18
+ ["--secure-heap=16384", "--secure-heap-min=8", entry, ...process.argv.slice(2)],
19
+ { stdio: "inherit" },
20
+ );
21
+ } catch (err) {
22
+ // If secure heap not available, run without it
23
+ if (err && typeof err === "object" && "status" in err) {
24
+ process.exit(err.status as number);
25
+ }
26
+ execFileSync(process.execPath, [entry, ...process.argv.slice(2)], {
27
+ stdio: "inherit",
28
+ });
29
+ }
@@ -0,0 +1,7 @@
1
+ import type { HibpEmailResult } from "../types/index.js";
2
+ /**
3
+ * Check an email against HIBP breached accounts, stealer logs, and paste endpoints.
4
+ * Requires a paid HIBP API key ($3.50/mo).
5
+ */
6
+ export declare function checkHibpEmail(email: string, apiKey: string): Promise<HibpEmailResult>;
7
+ //# sourceMappingURL=hibp-email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hibp-email.d.ts","sourceRoot":"","sources":["../../src/checks/hibp-email.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EAGhB,MAAM,mBAAmB,CAAC;AA0G3B;;;GAGG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CA0B1B"}
@@ -0,0 +1,99 @@
1
+ import { sanitizeForTerminal } from "../core/sanitize.js";
2
+ const HIBP_BASE = "https://haveibeenpwned.com/api/v3";
3
+ const USER_AGENT = "compromising-position/1.0.0";
4
+ const MAX_RETRIES = 1;
5
+ /** Delay for rate limiting. */
6
+ function delay(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ /**
10
+ * Fetch wrapper that:
11
+ * - Never exposes the API key in error messages
12
+ * - Handles 429 rate limiting with Retry-After header
13
+ */
14
+ async function hibpFetch(url, apiKey, retries = 0) {
15
+ let response;
16
+ try {
17
+ response = await fetch(url, {
18
+ headers: {
19
+ "hibp-api-key": apiKey,
20
+ "User-Agent": USER_AGENT,
21
+ },
22
+ });
23
+ }
24
+ catch (err) {
25
+ // Wrap network errors to prevent any header/key leakage
26
+ const msg = err instanceof Error ? err.message : "unknown error";
27
+ throw new Error(`HIBP request failed: ${sanitizeForTerminal(msg)}`);
28
+ }
29
+ // Handle rate limiting with exponential backoff
30
+ if (response.status === 429 && retries < MAX_RETRIES) {
31
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "2", 10);
32
+ const waitMs = (Number.isNaN(retryAfter) ? 2 : retryAfter) * 1000;
33
+ await delay(waitMs);
34
+ return hibpFetch(url, apiKey, retries + 1);
35
+ }
36
+ return response;
37
+ }
38
+ async function fetchBreaches(email, apiKey) {
39
+ const encoded = encodeURIComponent(email);
40
+ const response = await hibpFetch(`${HIBP_BASE}/breachedaccount/${encoded}?truncateResponse=false`, apiKey);
41
+ if (response.status === 404)
42
+ return [];
43
+ if (!response.ok) {
44
+ throw new Error(`Breaches API returned ${response.status}: ${sanitizeForTerminal(response.statusText)}`);
45
+ }
46
+ return (await response.json());
47
+ }
48
+ async function fetchStealerLogs(email, apiKey) {
49
+ const encoded = encodeURIComponent(email);
50
+ const response = await hibpFetch(`${HIBP_BASE}/stealerlogsbyemail/${encoded}`, apiKey);
51
+ if (response.status === 404)
52
+ return [];
53
+ if (!response.ok) {
54
+ throw new Error(`Stealer logs API returned ${response.status}: ${sanitizeForTerminal(response.statusText)}`);
55
+ }
56
+ return (await response.json());
57
+ }
58
+ async function fetchPastes(email, apiKey) {
59
+ const encoded = encodeURIComponent(email);
60
+ const response = await hibpFetch(`${HIBP_BASE}/pasteaccount/${encoded}`, apiKey);
61
+ if (response.status === 404)
62
+ return [];
63
+ if (!response.ok) {
64
+ throw new Error(`Pastes API returned ${response.status}: ${sanitizeForTerminal(response.statusText)}`);
65
+ }
66
+ return (await response.json());
67
+ }
68
+ /**
69
+ * Check an email against HIBP breached accounts, stealer logs, and paste endpoints.
70
+ * Requires a paid HIBP API key ($3.50/mo).
71
+ */
72
+ export async function checkHibpEmail(email, apiKey) {
73
+ try {
74
+ // Fetch sequentially to respect rate limits
75
+ const breaches = await fetchBreaches(email, apiKey);
76
+ await delay(1600);
77
+ const stealerLogs = await fetchStealerLogs(email, apiKey);
78
+ await delay(1600);
79
+ const pastes = await fetchPastes(email, apiKey);
80
+ return {
81
+ checked: true,
82
+ breaches,
83
+ stealerLogs,
84
+ pastes,
85
+ error: null,
86
+ };
87
+ }
88
+ catch (err) {
89
+ const message = err instanceof Error ? err.message : String(err);
90
+ return {
91
+ checked: true,
92
+ breaches: [],
93
+ stealerLogs: [],
94
+ pastes: [],
95
+ error: sanitizeForTerminal(message),
96
+ };
97
+ }
98
+ }
99
+ //# sourceMappingURL=hibp-email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hibp-email.js","sourceRoot":"","sources":["../../src/checks/hibp-email.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { SecureBuffer } from "../core/secure-buffer.js";
2
+ import type { HibpPasswordResult } from "../types/index.js";
3
+ /**
4
+ * Check a secret against the HIBP Pwned Passwords API using k-anonymity.
5
+ *
6
+ * Only the first 5 characters of the SHA-1 hash are sent to the server.
7
+ * The remaining 35 characters are compared locally using constant-time comparison.
8
+ *
9
+ * Uses Buffer-based SHA-1 computation to avoid leaving the full hash
10
+ * as an immutable string on the V8 heap.
11
+ */
12
+ export declare function checkHibpPassword(secret: SecureBuffer): Promise<HibpPasswordResult>;
13
+ //# sourceMappingURL=hibp-password.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hibp-password.d.ts","sourceRoot":"","sources":["../../src/checks/hibp-password.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAc5D;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,kBAAkB,CAAC,CA0D7B"}
@@ -0,0 +1,119 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { sanitizeForTerminal } from "../core/sanitize.js";
3
+ const HIBP_RANGE_URL = "https://api.pwnedpasswords.com/range/";
4
+ const USER_AGENT = "compromising-position/1.0.0";
5
+ /**
6
+ * Number of hex chars for the prefix (5 = 2.5 bytes of SHA-1).
7
+ * This is the only portion transmitted to the HIBP server.
8
+ */
9
+ const PREFIX_HEX_LENGTH = 5;
10
+ /** Number of hex chars for the suffix (35 = remaining SHA-1). */
11
+ const SUFFIX_HEX_LENGTH = 35;
12
+ /**
13
+ * Check a secret against the HIBP Pwned Passwords API using k-anonymity.
14
+ *
15
+ * Only the first 5 characters of the SHA-1 hash are sent to the server.
16
+ * The remaining 35 characters are compared locally using constant-time comparison.
17
+ *
18
+ * Uses Buffer-based SHA-1 computation to avoid leaving the full hash
19
+ * as an immutable string on the V8 heap.
20
+ */
21
+ export async function checkHibpPassword(secret) {
22
+ // Compute SHA-1 as raw bytes (20 bytes), not a hex string.
23
+ // This avoids leaving the full 40-char hex hash as an un-zeroable string.
24
+ const sha1Buf = secret.sha1Buffer();
25
+ // Extract prefix as hex for the API call (first 2.5 bytes = 5 hex chars).
26
+ // We need to read 3 bytes and take the first 5 hex chars.
27
+ const prefixBytes = sha1Buf.subarray(0, 3);
28
+ const prefix = prefixBytes.toString("hex").toUpperCase().slice(0, PREFIX_HEX_LENGTH);
29
+ // Build the suffix as a fixed-size uppercase hex Buffer for constant-time comparison.
30
+ const suffixHex = sha1Buf.subarray(2).toString("hex").toUpperCase().slice(1); // skip first nibble (already in prefix)
31
+ const suffixBuffer = Buffer.from(suffixHex, "utf-8");
32
+ // Zero the raw SHA-1 buffer now that we've extracted what we need
33
+ sha1Buf.fill(0);
34
+ try {
35
+ const response = await fetch(`${HIBP_RANGE_URL}${prefix}`, {
36
+ headers: {
37
+ "User-Agent": USER_AGENT,
38
+ "Add-Padding": "true",
39
+ },
40
+ });
41
+ if (!response.ok) {
42
+ return {
43
+ checked: true,
44
+ found: false,
45
+ occurrences: 0,
46
+ hashPrefix: prefix,
47
+ error: `HIBP API returned ${response.status}: ${sanitizeForTerminal(response.statusText)}`,
48
+ };
49
+ }
50
+ const body = await response.text();
51
+ const match = findMatchConstantTime(suffixBuffer, body);
52
+ return {
53
+ checked: true,
54
+ found: match.found,
55
+ occurrences: match.occurrences,
56
+ hashPrefix: prefix,
57
+ error: null,
58
+ };
59
+ }
60
+ catch (err) {
61
+ const message = err instanceof Error ? err.message : String(err);
62
+ return {
63
+ checked: true,
64
+ found: false,
65
+ occurrences: 0,
66
+ hashPrefix: prefix,
67
+ error: `Network error: ${sanitizeForTerminal(message)}`,
68
+ };
69
+ }
70
+ finally {
71
+ // Zero the suffix buffer
72
+ suffixBuffer.fill(0);
73
+ }
74
+ }
75
+ /**
76
+ * Compare our suffix against all returned suffixes using constant-time comparison.
77
+ *
78
+ * Every entry is compared with timingSafeEqual regardless of length match.
79
+ * Entries that don't match the expected length are padded to avoid skipping
80
+ * the comparison (which would create a timing side-channel).
81
+ *
82
+ * The loop never breaks early. Result variables are updated using the same
83
+ * code path on every iteration to minimize data-dependent timing variance.
84
+ */
85
+ function findMatchConstantTime(targetSuffix, responseBody) {
86
+ const targetLen = targetSuffix.length;
87
+ let found = false;
88
+ let occurrences = 0;
89
+ const lines = responseBody.split("\n");
90
+ for (const line of lines) {
91
+ const trimmed = line.trim();
92
+ if (trimmed.length === 0)
93
+ continue;
94
+ const colonIndex = trimmed.indexOf(":");
95
+ if (colonIndex === -1)
96
+ continue;
97
+ const entrySuffix = trimmed.slice(0, colonIndex);
98
+ const countStr = trimmed.slice(colonIndex + 1);
99
+ const count = parseInt(countStr, 10);
100
+ // Skip entries with invalid count (NaN, negative)
101
+ if (Number.isNaN(count) || count < 0)
102
+ continue;
103
+ // Pad or truncate entry to target length so we always call timingSafeEqual.
104
+ // This avoids a length-dependent branch that skips the comparison.
105
+ const entryBuf = Buffer.alloc(targetLen);
106
+ const entryRaw = Buffer.from(entrySuffix.toUpperCase(), "utf-8");
107
+ entryRaw.copy(entryBuf, 0, 0, Math.min(entryRaw.length, targetLen));
108
+ const lengthMatch = entryRaw.length === targetLen;
109
+ const bytesMatch = timingSafeEqual(entryBuf, targetSuffix);
110
+ // Both length and content must match. Use branchless-style update:
111
+ // always evaluate both conditions, never break early.
112
+ if (lengthMatch && bytesMatch) {
113
+ found = true;
114
+ occurrences = count;
115
+ }
116
+ }
117
+ return { found, occurrences };
118
+ }
119
+ //# sourceMappingURL=hibp-password.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hibp-password.js","sourceRoot":"","sources":["../../src/checks/hibp-password.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { SecureBuffer } from "../core/secure-buffer.js";
2
+ import { type LocalCheckResult } from "../types/index.js";
3
+ /**
4
+ * Perform local-only analysis: provider identification, entropy, format checks.
5
+ * Uses Buffer-based operations where possible to minimize the lifetime
6
+ * of secret data as immutable JS strings (which cannot be zeroed).
7
+ */
8
+ export declare function performLocalCheck(secret: SecureBuffer): LocalCheckResult;
9
+ //# sourceMappingURL=local-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-check.d.ts","sourceRoot":"","sources":["../../src/checks/local-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CAqCxE"}
@@ -0,0 +1,36 @@
1
+ import { identifyKey } from "../core/key-identifier.js";
2
+ import { analyzeEntropyFromBuffer } from "../core/entropy.js";
3
+ import { KeyProvider } from "../types/index.js";
4
+ /**
5
+ * Perform local-only analysis: provider identification, entropy, format checks.
6
+ * Uses Buffer-based operations where possible to minimize the lifetime
7
+ * of secret data as immutable JS strings (which cannot be zeroed).
8
+ */
9
+ export function performLocalCheck(secret) {
10
+ const identification = identifyKey(secret);
11
+ const entropy = analyzeEntropyFromBuffer(secret);
12
+ const warnings = [];
13
+ // Collect warnings
14
+ if (entropy.warning) {
15
+ warnings.push(entropy.warning);
16
+ }
17
+ if (entropy.length < 8) {
18
+ warnings.push("Input is very short — unlikely to be a real API key");
19
+ }
20
+ if (identification.provider === KeyProvider.StripeTest) {
21
+ warnings.push("This is a Stripe TEST key — not a production secret, but still should not be shared");
22
+ }
23
+ if (identification.provider === KeyProvider.Unknown && entropy.shannonEntropy < 3.0) {
24
+ warnings.push("Unrecognized format with low entropy — may be a password or placeholder");
25
+ }
26
+ // Heuristic: does this look like a real secret?
27
+ const looksLikeSecret = identification.provider !== KeyProvider.Unknown ||
28
+ (entropy.shannonEntropy >= 3.5 && entropy.length >= 16);
29
+ return {
30
+ identification,
31
+ entropy,
32
+ warnings,
33
+ looksLikeSecret,
34
+ };
35
+ }
36
+ //# sourceMappingURL=local-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-check.js","sourceRoot":"","sources":["../../src/checks/local-check.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAyB,MAAM,mBAAmB,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,mBAAmB;IACnB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,cAAc,CAAC,QAAQ,KAAK,WAAW,CAAC,UAAU,EAAE,CAAC;QACvD,QAAQ,CAAC,IAAI,CACX,qFAAqF,CACtF,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,CAAC,QAAQ,KAAK,WAAW,CAAC,OAAO,IAAI,OAAO,CAAC,cAAc,GAAG,GAAG,EAAE,CAAC;QACpF,QAAQ,CAAC,IAAI,CACX,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IAED,gDAAgD;IAChD,MAAM,eAAe,GACnB,cAAc,CAAC,QAAQ,KAAK,WAAW,CAAC,OAAO;QAC/C,CAAC,OAAO,CAAC,cAAc,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAE1D,OAAO;QACL,cAAc;QACd,OAAO;QACP,QAAQ;QACR,eAAe;KAChB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { SecureBuffer } from "../core/secure-buffer.js";
2
+ import type { AppConfig, PluginCheckResult, PluginInputKind } from "../types/index.js";
3
+ /**
4
+ * Interface for check plugins. Each plugin checks a secret or email
5
+ * against a specific data source and returns a standardized result.
6
+ */
7
+ export interface CheckPlugin {
8
+ /** Unique identifier for this plugin. */
9
+ readonly id: string;
10
+ /** Human-readable name for display. */
11
+ readonly name: string;
12
+ /** What kind of input this plugin operates on. */
13
+ readonly inputKind: PluginInputKind;
14
+ /** Whether this plugin makes network requests. */
15
+ readonly requiresNetwork: boolean;
16
+ /** Config keys (env var names) that must be set for this plugin to run. */
17
+ readonly requiredConfigKeys: string[];
18
+ /** Whether this plugin is free (no API key purchase needed). */
19
+ readonly isFree: boolean;
20
+ /** Short description of what data is sent and where, for --privacy output. */
21
+ readonly privacySummary: string;
22
+ /**
23
+ * Run the check.
24
+ * @param input - The secret (SecureBuffer) or email (string), depending on inputKind.
25
+ * @param config - App configuration including API keys.
26
+ */
27
+ check(input: SecureBuffer | string, config: AppConfig): Promise<PluginCheckResult>;
28
+ }
29
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/checks/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EACjB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IAEpC,kDAAkD;IAClD,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAElC,2EAA2E;IAC3E,QAAQ,CAAC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAEtC,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzB,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC;;;;OAIG;IACH,KAAK,CACH,KAAK,EAAE,YAAY,GAAG,MAAM,EAC5B,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/checks/plugin.ts"],"names":[],"mappings":""}