code-secret-handler 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 (171) hide show
  1. package/LICENSE +79 -0
  2. package/README.md +221 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +121 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/env/envWriter.d.ts +9 -0
  8. package/dist/env/envWriter.d.ts.map +1 -0
  9. package/dist/env/envWriter.js +76 -0
  10. package/dist/env/envWriter.js.map +1 -0
  11. package/dist/env/keyNamer.d.ts +13 -0
  12. package/dist/env/keyNamer.d.ts.map +1 -0
  13. package/dist/env/keyNamer.js +33 -0
  14. package/dist/env/keyNamer.js.map +1 -0
  15. package/dist/interactive/confirm.d.ts +16 -0
  16. package/dist/interactive/confirm.d.ts.map +1 -0
  17. package/dist/interactive/confirm.js +72 -0
  18. package/dist/interactive/confirm.js.map +1 -0
  19. package/dist/license/gitContext.d.ts +16 -0
  20. package/dist/license/gitContext.d.ts.map +1 -0
  21. package/dist/license/gitContext.js +117 -0
  22. package/dist/license/gitContext.js.map +1 -0
  23. package/dist/license/licenseCheck.d.ts +22 -0
  24. package/dist/license/licenseCheck.d.ts.map +1 -0
  25. package/dist/license/licenseCheck.js +119 -0
  26. package/dist/license/licenseCheck.js.map +1 -0
  27. package/dist/license/telemetry.d.ts +17 -0
  28. package/dist/license/telemetry.d.ts.map +1 -0
  29. package/dist/license/telemetry.js +87 -0
  30. package/dist/license/telemetry.js.map +1 -0
  31. package/dist/reporter/console.d.ts +3 -0
  32. package/dist/reporter/console.d.ts.map +1 -0
  33. package/dist/reporter/console.js +96 -0
  34. package/dist/reporter/console.js.map +1 -0
  35. package/dist/reporter/csv.d.ts +6 -0
  36. package/dist/reporter/csv.d.ts.map +1 -0
  37. package/dist/reporter/csv.js +56 -0
  38. package/dist/reporter/csv.js.map +1 -0
  39. package/dist/reporter/index.d.ts +13 -0
  40. package/dist/reporter/index.d.ts.map +1 -0
  41. package/dist/reporter/index.js +52 -0
  42. package/dist/reporter/index.js.map +1 -0
  43. package/dist/reporter/json.d.ts +7 -0
  44. package/dist/reporter/json.d.ts.map +1 -0
  45. package/dist/reporter/json.js +39 -0
  46. package/dist/reporter/json.js.map +1 -0
  47. package/dist/reporter/markdown.d.ts +6 -0
  48. package/dist/reporter/markdown.d.ts.map +1 -0
  49. package/dist/reporter/markdown.js +106 -0
  50. package/dist/reporter/markdown.js.map +1 -0
  51. package/dist/rewriter/index.d.ts +9 -0
  52. package/dist/rewriter/index.d.ts.map +1 -0
  53. package/dist/rewriter/index.js +95 -0
  54. package/dist/rewriter/index.js.map +1 -0
  55. package/dist/rewriter/langDetector.d.ts +7 -0
  56. package/dist/rewriter/langDetector.d.ts.map +1 -0
  57. package/dist/rewriter/langDetector.js +96 -0
  58. package/dist/rewriter/langDetector.js.map +1 -0
  59. package/dist/rewriter/templates/csharp.d.ts +8 -0
  60. package/dist/rewriter/templates/csharp.d.ts.map +1 -0
  61. package/dist/rewriter/templates/csharp.js +36 -0
  62. package/dist/rewriter/templates/csharp.js.map +1 -0
  63. package/dist/rewriter/templates/generic.d.ts +7 -0
  64. package/dist/rewriter/templates/generic.d.ts.map +1 -0
  65. package/dist/rewriter/templates/generic.js +40 -0
  66. package/dist/rewriter/templates/generic.js.map +1 -0
  67. package/dist/rewriter/templates/go.d.ts +8 -0
  68. package/dist/rewriter/templates/go.d.ts.map +1 -0
  69. package/dist/rewriter/templates/go.js +56 -0
  70. package/dist/rewriter/templates/go.js.map +1 -0
  71. package/dist/rewriter/templates/java.d.ts +7 -0
  72. package/dist/rewriter/templates/java.d.ts.map +1 -0
  73. package/dist/rewriter/templates/java.js +31 -0
  74. package/dist/rewriter/templates/java.js.map +1 -0
  75. package/dist/rewriter/templates/javascript.d.ts +10 -0
  76. package/dist/rewriter/templates/javascript.d.ts.map +1 -0
  77. package/dist/rewriter/templates/javascript.js +54 -0
  78. package/dist/rewriter/templates/javascript.js.map +1 -0
  79. package/dist/rewriter/templates/php.d.ts +8 -0
  80. package/dist/rewriter/templates/php.d.ts.map +1 -0
  81. package/dist/rewriter/templates/php.js +41 -0
  82. package/dist/rewriter/templates/php.js.map +1 -0
  83. package/dist/rewriter/templates/properties.d.ts +9 -0
  84. package/dist/rewriter/templates/properties.d.ts.map +1 -0
  85. package/dist/rewriter/templates/properties.js +39 -0
  86. package/dist/rewriter/templates/properties.js.map +1 -0
  87. package/dist/rewriter/templates/python.d.ts +8 -0
  88. package/dist/rewriter/templates/python.d.ts.map +1 -0
  89. package/dist/rewriter/templates/python.js +48 -0
  90. package/dist/rewriter/templates/python.js.map +1 -0
  91. package/dist/rewriter/templates/ruby.d.ts +8 -0
  92. package/dist/rewriter/templates/ruby.d.ts.map +1 -0
  93. package/dist/rewriter/templates/ruby.js +40 -0
  94. package/dist/rewriter/templates/ruby.js.map +1 -0
  95. package/dist/rewriter/templates/rust.d.ts +7 -0
  96. package/dist/rewriter/templates/rust.d.ts.map +1 -0
  97. package/dist/rewriter/templates/rust.js +27 -0
  98. package/dist/rewriter/templates/rust.js.map +1 -0
  99. package/dist/rewriter/templates/shell.d.ts +7 -0
  100. package/dist/rewriter/templates/shell.d.ts.map +1 -0
  101. package/dist/rewriter/templates/shell.js +42 -0
  102. package/dist/rewriter/templates/shell.js.map +1 -0
  103. package/dist/rewriter/templates/toml.d.ts +9 -0
  104. package/dist/rewriter/templates/toml.d.ts.map +1 -0
  105. package/dist/rewriter/templates/toml.js +34 -0
  106. package/dist/rewriter/templates/toml.js.map +1 -0
  107. package/dist/rewriter/templates/yaml.d.ts +8 -0
  108. package/dist/rewriter/templates/yaml.d.ts.map +1 -0
  109. package/dist/rewriter/templates/yaml.js +37 -0
  110. package/dist/rewriter/templates/yaml.js.map +1 -0
  111. package/dist/scanner/detector.d.ts +18 -0
  112. package/dist/scanner/detector.d.ts.map +1 -0
  113. package/dist/scanner/detector.js +156 -0
  114. package/dist/scanner/detector.js.map +1 -0
  115. package/dist/scanner/fileWalker.d.ts +6 -0
  116. package/dist/scanner/fileWalker.d.ts.map +1 -0
  117. package/dist/scanner/fileWalker.js +146 -0
  118. package/dist/scanner/fileWalker.js.map +1 -0
  119. package/dist/scanner/index.d.ts +8 -0
  120. package/dist/scanner/index.d.ts.map +1 -0
  121. package/dist/scanner/index.js +74 -0
  122. package/dist/scanner/index.js.map +1 -0
  123. package/dist/scanner/rules/aws.d.ts +6 -0
  124. package/dist/scanner/rules/aws.d.ts.map +1 -0
  125. package/dist/scanner/rules/aws.js +90 -0
  126. package/dist/scanner/rules/aws.js.map +1 -0
  127. package/dist/scanner/rules/azure.d.ts +6 -0
  128. package/dist/scanner/rules/azure.d.ts.map +1 -0
  129. package/dist/scanner/rules/azure.js +86 -0
  130. package/dist/scanner/rules/azure.js.map +1 -0
  131. package/dist/scanner/rules/cloud.d.ts +11 -0
  132. package/dist/scanner/rules/cloud.d.ts.map +1 -0
  133. package/dist/scanner/rules/cloud.js +1066 -0
  134. package/dist/scanner/rules/cloud.js.map +1 -0
  135. package/dist/scanner/rules/database.d.ts +6 -0
  136. package/dist/scanner/rules/database.d.ts.map +1 -0
  137. package/dist/scanner/rules/database.js +283 -0
  138. package/dist/scanner/rules/database.js.map +1 -0
  139. package/dist/scanner/rules/gcp.d.ts +6 -0
  140. package/dist/scanner/rules/gcp.d.ts.map +1 -0
  141. package/dist/scanner/rules/gcp.js +108 -0
  142. package/dist/scanner/rules/gcp.js.map +1 -0
  143. package/dist/scanner/rules/generic.d.ts +13 -0
  144. package/dist/scanner/rules/generic.d.ts.map +1 -0
  145. package/dist/scanner/rules/generic.js +305 -0
  146. package/dist/scanner/rules/generic.js.map +1 -0
  147. package/dist/scanner/rules/index.d.ts +18 -0
  148. package/dist/scanner/rules/index.d.ts.map +1 -0
  149. package/dist/scanner/rules/index.js +40 -0
  150. package/dist/scanner/rules/index.js.map +1 -0
  151. package/dist/scanner/rules/jwt.d.ts +6 -0
  152. package/dist/scanner/rules/jwt.d.ts.map +1 -0
  153. package/dist/scanner/rules/jwt.js +97 -0
  154. package/dist/scanner/rules/jwt.js.map +1 -0
  155. package/dist/scanner/rules/network.d.ts +7 -0
  156. package/dist/scanner/rules/network.d.ts.map +1 -0
  157. package/dist/scanner/rules/network.js +166 -0
  158. package/dist/scanner/rules/network.js.map +1 -0
  159. package/dist/scanner/rules/privateKey.d.ts +6 -0
  160. package/dist/scanner/rules/privateKey.d.ts.map +1 -0
  161. package/dist/scanner/rules/privateKey.js +60 -0
  162. package/dist/scanner/rules/privateKey.js.map +1 -0
  163. package/dist/scanner/rules/ssh.d.ts +6 -0
  164. package/dist/scanner/rules/ssh.d.ts.map +1 -0
  165. package/dist/scanner/rules/ssh.js +49 -0
  166. package/dist/scanner/rules/ssh.js.map +1 -0
  167. package/dist/types.d.ts +78 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +3 -0
  170. package/dist/types.js.map +1 -0
  171. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,79 @@
1
+ Copyright (c) 2024 Vic (vic.grambo@gmail.com)
2
+
3
+ PolyForm Noncommercial License 1.0.0
4
+
5
+ Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ Use Grant
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case for your personal and noncommercial purposes only.
12
+
13
+ Noncommercial Purposes
14
+
15
+ Any of the following uses of the software counts as a noncommercial purpose:
16
+
17
+ * Personal research, study, learning, private entertainment, or hobby projects without any anticipated commercial application.
18
+ * Use by a charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution.
19
+
20
+ Commercial Use Reserved
21
+
22
+ Any use of the software to directly or indirectly generate revenue, reduce costs, or obtain a commercial advantage for a business, company, organization, or for-profit entity requires a separate commercial license granted in writing by the licensor.
23
+
24
+ This includes, but is not limited to:
25
+ * Use within a company, startup, agency, or any for-profit organization.
26
+ * Running the tool as part of an automated CI/CD pipeline in a commercial context.
27
+ * Offering services built on or using this tool to paying customers.
28
+ * Any use by a GitHub Organization, GitLab Group, or equivalent entity.
29
+
30
+ Commercial Licensing
31
+
32
+ To obtain a commercial license, please contact:
33
+
34
+ Vic — vic.grambo@gmail.com
35
+
36
+ Commercial licensees will receive a license key to be set as:
37
+ CSH_LICENSE_KEY=<your-key>
38
+
39
+ Notices
40
+
41
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with "Required Notice:" that the licensor provides with the software.
42
+
43
+ No Other Rights
44
+
45
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else.
46
+
47
+ Patent Defense
48
+
49
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your license for the software granted under these terms ends immediately. If your company makes such a claim, your license ends immediately for work on behalf of your company.
50
+
51
+ Violations
52
+
53
+ The first time you are notified in writing that you have violated any of these terms, your license can nonetheless continue if you come into full compliance with these terms within 30 days of receiving that notice. If any violation of these terms is willful, the 30-day cure period above does not apply.
54
+
55
+ No Liability
56
+
57
+ As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
58
+
59
+ Telemetry Disclosure
60
+
61
+ This software collects anonymous usage telemetry to assist with license compliance monitoring. The following data is collected when the tool is run:
62
+
63
+ * Tool version, operating system, and Node.js version
64
+ * Git remote URL, repository owner, and repository name
65
+ * Git platform (GitHub, GitLab, Bitbucket, or other)
66
+ * Whether the repository owner is an Organization or personal User
67
+ * Whether a commercial license key was provided
68
+ * Timestamp of the run
69
+
70
+ No source code, file contents, secret values, or personally identifiable information is ever collected.
71
+
72
+ To opt out of telemetry collection, set the following environment variable:
73
+ CSH_TELEMETRY=0
74
+
75
+ Definitions
76
+
77
+ "The licensor" refers to Vic (vic.grambo@gmail.com), the individual who makes the software available under these terms.
78
+
79
+ "The software" refers to the code-secret-handler CLI tool and all associated source code, binaries, and documentation made available under this license.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # 🔐 Code Secret Handler
2
+
3
+ A **language-agnostic CLI tool** that scans any codebase for hardcoded secrets, lists them with full context, moves them to a `.env` file, and rewrites source files with the correct environment variable accessor for each language.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - 🔍 **Deep scanning** — 179+ secret types detected across 10 rule categories (AWS, GCP, Azure, GitHub, Stripe, Twilio, OpenAI, Anthropic, Discord, Telegram, Supabase, JWT, SSH keys, database URIs, SMTP, and many more)
10
+ - 🌐 **Language-aware rewriting** — auto-detects file language and uses the correct env-var syntax:
11
+ - `process.env.KEY` (JavaScript/TypeScript)
12
+ - `os.environ.get('KEY')` (Python)
13
+ - `System.getenv("KEY")` (Java/Kotlin)
14
+ - `os.Getenv("KEY")` (Go)
15
+ - `ENV['KEY']` (Ruby)
16
+ - `getenv('KEY')` (PHP)
17
+ - `Environment.GetEnvironmentVariable("KEY")` (C#)
18
+ - `std::env::var("KEY")` (Rust)
19
+ - `$KEY` / `$env:KEY` (Shell/PowerShell)
20
+ - `${KEY}` (YAML, TOML, properties files)
21
+ - 📋 **Multiple report formats** — console (colored), JSON, CSV, Markdown
22
+ - 🔒 **Collision-safe** — appends `_2`, `_3`, etc. for duplicate env key names
23
+ - 🧠 **Respects `.gitignore`** — only scans tracked files, skips `node_modules`, `dist`, etc.
24
+ - 🛡️ **Interactive** — asks before making any changes, with backup option
25
+ - 🔍 **Dry-run mode** — scan and report without modifying anything
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ # Run directly with npx (no install needed)
33
+ npx code-secret-handler ./my-project
34
+
35
+ # Or install globally
36
+ npm install -g code-secret-handler
37
+ csh ./my-project
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Usage
43
+
44
+ ```bash
45
+ csh [target-dir] [options]
46
+ ```
47
+
48
+ ### Options
49
+
50
+ | Flag | Description | Default |
51
+ |------|-------------|---------|
52
+ | `[target-dir]` | Directory to scan | `.` (current dir) |
53
+ | `-r, --report <format>` | Report format: `console` \| `json` \| `csv` \| `markdown` | `console` |
54
+ | `-o, --output <file>` | Report output file path | Auto-derived |
55
+ | `-e, --env-file <path>` | Target `.env` file path | `./.env` |
56
+ | `--dry-run` | Scan only — no file modifications | `false` |
57
+ | `--no-rewrite` | Write `.env` but skip source-file rewriting | `false` |
58
+ | `-v, --version` | Show version | |
59
+ | `-h, --help` | Show help | |
60
+
61
+ ### Examples
62
+
63
+ ```bash
64
+ # Scan current directory (interactive)
65
+ csh .
66
+
67
+ # Dry run — see what would be found without changes
68
+ csh ./my-app --dry-run
69
+
70
+ # Write Markdown report to a file
71
+ csh ./my-app -r markdown -o security-report.md
72
+
73
+ # Use a custom .env file location
74
+ csh ./my-app -e config/.env
75
+
76
+ # Write .env but do NOT rewrite source files
77
+ csh ./my-app --no-rewrite
78
+
79
+ # JSON report
80
+ csh ./my-app -r json -o findings.json
81
+
82
+ # CSV report for spreadsheet import
83
+ csh ./my-app -r csv
84
+ ```
85
+
86
+ ---
87
+
88
+ ## How It Works
89
+
90
+ ```
91
+ CLI args
92
+ └─► File Walker (respects .gitignore)
93
+ └─► Detector (179+ regex rules, line-by-line)
94
+ └─► Findings { file, line, col, type, rawValue, envKey }
95
+ ├─► Reporter (console/json/csv/markdown)
96
+ └─► [Interactive confirmation]
97
+ ├─► Env Writer → .env
98
+ └─► Rewriter → patched source files
99
+ ```
100
+
101
+ ### Detection Rules
102
+
103
+ | Category | Rules | Examples |
104
+ |----------|-------|----------|
105
+ | AWS | 10 | Access Key ID (`AKIA…`), Secret Key, Session Token, ARN, S3 bucket, MWS key, CloudFront |
106
+ | GCP | 11 | API Key (`AIza…`), OAuth Client, Service Account, Maps, reCAPTCHA, Firebase DB URL |
107
+ | Azure | 8 | Storage connection strings, SAS tokens, Service Bus, CosmosDB, Client/Tenant ID |
108
+ | GitHub | 4 | `ghp_`, `gho_`, `ghs_`, `github_pat_` tokens |
109
+ | GitLab | 2 | `glpat-` Personal Access Tokens, Runner registration tokens |
110
+ | Stripe | 3 | `sk_live_`, `sk_test_`, `rk_live_`, `whsec_` webhook secrets |
111
+ | Twilio | 3 | Account SID, Auth Token, API Key (`SK…`) |
112
+ | Slack | 3 | `xoxb-`/`xoxp-` tokens, webhook URLs, signing secrets |
113
+ | JWT / Auth | 11 | JWT secrets, session, NextAuth, Flask, Rails `secret_key_base`, CSRF, OAuth2 |
114
+ | SSH / PEM | 10 | RSA, EC, DSA, OpenSSH, PGP private key blocks, PKCS12 |
115
+ | Database | 19 | PostgreSQL, MySQL, MongoDB, Redis, MSSQL, Elasticsearch, RabbitMQ, Cassandra, Oracle, CockroachDB, Neo4j, InfluxDB, Kafka |
116
+ | Network | 12 | SMTP, proxy URLs, LDAP, CI/CD deploy tokens, Apple APNs, webhook URLs |
117
+ | AI / ML | 6 | OpenAI (`sk-`), Anthropic (`sk-ant-`), Hugging Face (`hf_`), Pinecone, Cohere, Replicate (`r8_`) |
118
+ | Payments | 5 | Square (`EAA…`), Razorpay, Braintree, Plaid, PayPal |
119
+ | SaaS | 8 | Notion (`secret_`), Linear (`lin_api_`), Airtable, HubSpot, Zendesk, Intercom, Jira, PagerDuty |
120
+ | DevOps | 7 | Vercel, DigitalOcean, Terraform Cloud, HashiCorp Vault, Confluent, Docker Hub, Heroku |
121
+ | Analytics | 8 | Amplitude, Mixpanel, Segment, New Relic (`NRAK-`), Datadog, Dynatrace, Grafana, Sentry |
122
+ | Generic | 21 | API keys/secrets, passwords, bearer tokens, HMAC keys, webhook secrets, license keys, connection strings |
123
+ | Other | 16 | Cloudflare, Cloudinary, Algolia, Mapbox, Shopify, Auth0, Okta, Firebase, LaunchDarkly, Discord, Telegram, Mailgun, Mailchimp, SendGrid, NPM, Pusher |
124
+
125
+ ---
126
+
127
+ ## After Running
128
+
129
+ 1. **Verify** the `.env` file was created with the correct values.
130
+ 2. **Add `.env` to `.gitignore`** — the tool reminds you, but please double-check!
131
+ 3. **Rotate secrets** that may have been committed to Git history previously.
132
+ 4. **Review** modified source files before committing.
133
+ 5. **Share `.env.example`** (with placeholder values) with your team.
134
+
135
+ ---
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ git clone <repo>
141
+ cd code-secret-handler
142
+ npm install
143
+
144
+ # Run in dev mode
145
+ npm run dev -- ./path/to/scan
146
+
147
+ # Build
148
+ npm run build
149
+
150
+ # Type-check
151
+ npm run lint
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Telemetry
157
+
158
+ `code-secret-handler` collects **anonymous usage telemetry** to help the author monitor license compliance and understand how the tool is being used.
159
+
160
+ ### What is collected
161
+
162
+ | Data | Example |
163
+ |------|---------|
164
+ | Tool version | `1.0.0` |
165
+ | OS platform | `linux`, `darwin`, `win32` |
166
+ | Node.js version | `v20.11.0` |
167
+ | Git remote URL | `https://github.com/owner/repo` |
168
+ | Repo owner & name | `owner`, `repo` |
169
+ | Git platform | `github`, `gitlab`, `bitbucket` |
170
+ | Account type | `Organization` or `User` |
171
+ | License key provided | `true` / `false` |
172
+ | Timestamp | ISO 8601 |
173
+
174
+ ### What is NOT collected
175
+
176
+ - ❌ Source code or file contents
177
+ - ❌ Secret values or credentials
178
+ - ❌ Personal identifiable information
179
+ - ❌ Anything beyond the fields listed above
180
+
181
+ ### Opt out
182
+
183
+ Set `CSH_TELEMETRY=0` in your environment to disable all telemetry:
184
+
185
+ ```bash
186
+ CSH_TELEMETRY=0 csh ./my-project
187
+ # or permanently in your shell profile:
188
+ export CSH_TELEMETRY=0
189
+ ```
190
+
191
+ ---
192
+
193
+ ## License
194
+
195
+ **PolyForm Noncommercial License 1.0.0**
196
+ Copyright (c) 2024 Vic — vic.grambo@gmail.com
197
+
198
+ ### ✅ Free for
199
+
200
+ - Individual developers for personal projects, learning, research, or hobby use
201
+ - Non-profit organizations, educational institutions, public research organizations, and government institutions
202
+
203
+ ### ❌ Not free for
204
+
205
+ - Companies, startups, agencies, or any for-profit organization
206
+ - Any use that directly or indirectly generates revenue or commercial advantage
207
+ - Running the tool within a commercial CI/CD pipeline or product
208
+
209
+ ### 💼 Commercial licensing
210
+
211
+ If you represent an organization or wish to use this tool commercially, please contact:
212
+
213
+ **vic.grambo@gmail.com**
214
+
215
+ Commercial licensees receive a license key. Set it as an environment variable before running the tool:
216
+
217
+ ```bash
218
+ CSH_LICENSE_KEY=<your-key> csh ./my-project
219
+ ```
220
+
221
+ The full license text is available in the [LICENSE](./LICENSE) file.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const scanner_1 = require("./scanner");
12
+ const envWriter_1 = require("./env/envWriter");
13
+ const rewriter_1 = require("./rewriter");
14
+ const reporter_1 = require("./reporter");
15
+ const confirm_1 = require("./interactive/confirm");
16
+ const gitContext_1 = require("./license/gitContext");
17
+ const licenseCheck_1 = require("./license/licenseCheck");
18
+ const telemetry_1 = require("./license/telemetry");
19
+ const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', 'package.json'), 'utf-8'));
20
+ const program = new commander_1.Command();
21
+ program
22
+ .name('code-secret-handler')
23
+ .alias('csh')
24
+ .description(chalk_1.default.cyan('🔐 Scans your codebase for hardcoded secrets, moves them to .env, and rewrites source files.'))
25
+ .version(pkg.version, '-v, --version', 'Output the current version')
26
+ .argument('[target-dir]', 'Directory to scan (default: current directory)', '.')
27
+ .option('-r, --report <format>', 'Report format: console | json | csv | markdown', 'console')
28
+ .option('-o, --output <file>', 'Report output file path (auto-derived if omitted)')
29
+ .option('-e, --env-file <path>', 'Target .env file path', '.env')
30
+ .option('--dry-run', 'Scan and report only — do not write any files', false)
31
+ .option('--no-rewrite', 'Write .env but skip source-file rewriting')
32
+ .addHelpText('after', `
33
+ ${chalk_1.default.bold('Examples:')}
34
+ ${chalk_1.default.cyan('$ csh .')} Scan current directory
35
+ ${chalk_1.default.cyan('$ csh ./my-app --dry-run')} Scan without modifying files
36
+ ${chalk_1.default.cyan('$ csh ./my-app -r markdown -o report.md')} Scan and write Markdown report
37
+ ${chalk_1.default.cyan('$ csh ./my-app -e secrets/.env')} Use a custom .env path
38
+ ${chalk_1.default.cyan('$ csh ./my-app --no-rewrite')} Write .env only, skip source rewrites
39
+ `);
40
+ program.action(async (targetDirArg, opts) => {
41
+ console.log(chalk_1.default.bold.cyan('\n🔐 Code Secret Handler\n'));
42
+ const targetDir = path_1.default.resolve(targetDirArg || '.');
43
+ // ── License & telemetry (runs before anything else) ──────────────────────
44
+ const gitCtx = await (0, gitContext_1.getGitContext)(targetDir);
45
+ const { licenseKeyProvided } = await (0, licenseCheck_1.checkLicense)(gitCtx);
46
+ (0, telemetry_1.sendTelemetry)(gitCtx, licenseKeyProvided);
47
+ // ─────────────────────────────────────────────────────────────────────────
48
+ const report = (opts['report'] || 'console').toLowerCase();
49
+ const envFile = path_1.default.resolve(targetDir, opts['envFile'] || '.env');
50
+ const dryRun = Boolean(opts['dryRun']);
51
+ const noRewrite = !Boolean(opts['rewrite']);
52
+ const outputArg = opts['output'];
53
+ const outputPath = outputArg
54
+ ? path_1.default.resolve(outputArg)
55
+ : report !== 'console'
56
+ ? (0, reporter_1.defaultOutputPath)(report, targetDir)
57
+ : undefined;
58
+ const _options = { targetDir, report, output: outputPath, envFile, dryRun, noRewrite };
59
+ if (!fs_1.default.existsSync(targetDir) || !fs_1.default.statSync(targetDir).isDirectory()) {
60
+ console.error(chalk_1.default.red(`❌ Target directory not found: ${targetDir}`));
61
+ process.exit(1);
62
+ }
63
+ const validFormats = ['console', 'json', 'csv', 'markdown'];
64
+ if (!validFormats.includes(report)) {
65
+ console.error(chalk_1.default.red(`❌ Invalid report format: "${report}". Choose from: ${validFormats.join(', ')}`));
66
+ process.exit(1);
67
+ }
68
+ let summary = await (0, scanner_1.runScanner)(targetDir, envFile);
69
+ summary.dryRun = dryRun;
70
+ if (outputPath)
71
+ summary.reportFile = outputPath;
72
+ const reportFile = (0, reporter_1.runReporter)(report, summary, outputPath);
73
+ if (reportFile) {
74
+ console.log(chalk_1.default.green(`\n 📄 Report written to: ${chalk_1.default.underline(reportFile)}\n`));
75
+ }
76
+ if (summary.totalFindings === 0)
77
+ process.exit(0);
78
+ if (dryRun) {
79
+ console.log(chalk_1.default.yellow(' ℹ️ Dry run mode — no changes applied.\n'));
80
+ process.exit(0);
81
+ }
82
+ const approved = await (0, confirm_1.confirmChanges)(summary, noRewrite);
83
+ if (!approved) {
84
+ console.log(chalk_1.default.yellow('\n ❌ Aborted — no changes made.\n'));
85
+ process.exit(0);
86
+ }
87
+ let backupChoice = 'skip';
88
+ if (!noRewrite && summary.totalFindings > 0) {
89
+ backupChoice = await (0, confirm_1.askBackupPreference)();
90
+ if (backupChoice === 'abort') {
91
+ console.log(chalk_1.default.yellow('\n ❌ Aborted — no changes made.\n'));
92
+ process.exit(0);
93
+ }
94
+ }
95
+ const written = (0, envWriter_1.writeEnvFile)(envFile, summary.findings);
96
+ console.log(chalk_1.default.green(`\n ✅ ${written} secret(s) written to ${chalk_1.default.underline(envFile)}`));
97
+ if (!noRewrite) {
98
+ const results = (0, rewriter_1.rewriteFiles)(summary.findings, false);
99
+ let totalRewrites = 0;
100
+ for (const result of results) {
101
+ if (result.replacements === 0)
102
+ continue;
103
+ if (backupChoice === 'backup') {
104
+ fs_1.default.writeFileSync(`${result.filePath}.bak`, result.originalContent, 'utf-8');
105
+ }
106
+ totalRewrites += result.replacements;
107
+ const relPath = path_1.default.relative(targetDir, result.filePath);
108
+ console.log(chalk_1.default.cyan(` ✏️ ${relPath}`) +
109
+ chalk_1.default.gray(` — ${result.replacements} replacement(s)`) +
110
+ (result.dotenvInjected ? chalk_1.default.yellow(' + dotenv injected') : ''));
111
+ }
112
+ console.log(chalk_1.default.green(`\n ✅ ${totalRewrites} replacement(s) across ${results.filter(r => r.replacements > 0).length} file(s)`));
113
+ }
114
+ console.log(chalk_1.default.bold.yellow('\n ⚠️ IMPORTANT:'));
115
+ console.log(chalk_1.default.yellow(' 1. Add .env to your .gitignore if not already done.'));
116
+ console.log(chalk_1.default.yellow(' 2. Rotate any secrets that may have been committed to Git history.'));
117
+ console.log(chalk_1.default.yellow(' 3. Review all modified source files before committing.\n'));
118
+ process.exit(0);
119
+ });
120
+ program.parse(process.argv);
121
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Finding } from '../types';
2
+ /**
3
+ * Appends or creates a .env file with the given findings.
4
+ * Keys that already exist in the file are skipped (deduplication was done earlier).
5
+ *
6
+ * @returns number of entries written
7
+ */
8
+ export declare function writeEnvFile(envFilePath: string, findings: Finding[]): number;
9
+ //# sourceMappingURL=envWriter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envWriter.d.ts","sourceRoot":"","sources":["../../src/env/envWriter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAsBnC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CA6C7E"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeEnvFile = writeEnvFile;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Parses an existing .env file into a map of key → value.
11
+ */
12
+ function parseEnvFile(filePath) {
13
+ const map = new Map();
14
+ if (!fs_1.default.existsSync(filePath))
15
+ return map;
16
+ const lines = fs_1.default.readFileSync(filePath, 'utf-8').split('\n');
17
+ for (const line of lines) {
18
+ const trimmed = line.trim();
19
+ if (!trimmed || trimmed.startsWith('#'))
20
+ continue;
21
+ const eqIdx = trimmed.indexOf('=');
22
+ if (eqIdx < 1)
23
+ continue;
24
+ const key = trimmed.slice(0, eqIdx).trim();
25
+ const value = trimmed.slice(eqIdx + 1).trim().replace(/^['"`]|['"`]$/g, '');
26
+ map.set(key, value);
27
+ }
28
+ return map;
29
+ }
30
+ /**
31
+ * Appends or creates a .env file with the given findings.
32
+ * Keys that already exist in the file are skipped (deduplication was done earlier).
33
+ *
34
+ * @returns number of entries written
35
+ */
36
+ function writeEnvFile(envFilePath, findings) {
37
+ const absPath = path_1.default.resolve(envFilePath);
38
+ // Ensure parent dir exists
39
+ const dir = path_1.default.dirname(absPath);
40
+ if (!fs_1.default.existsSync(dir)) {
41
+ fs_1.default.mkdirSync(dir, { recursive: true });
42
+ }
43
+ const existing = parseEnvFile(absPath);
44
+ const lines = [];
45
+ let written = 0;
46
+ // Group findings by source file for a readable header
47
+ const byFile = new Map();
48
+ for (const f of findings) {
49
+ if (!byFile.has(f.file))
50
+ byFile.set(f.file, []);
51
+ byFile.get(f.file).push(f);
52
+ }
53
+ const header = `\n# ─── Added by code-secret-handler on ${new Date().toISOString()} ───\n`;
54
+ lines.push(header);
55
+ for (const [file, fileFindings] of byFile) {
56
+ lines.push(`# Source: ${file}`);
57
+ for (const finding of fileFindings) {
58
+ if (existing.has(finding.envKey)) {
59
+ // Should not happen due to dedup, but be safe
60
+ finding.alreadyInEnv = true;
61
+ continue;
62
+ }
63
+ // Escape value: wrap in double quotes, escape embedded double quotes
64
+ const safeValue = finding.rawValue.replace(/"/g, '\\"');
65
+ lines.push(`${finding.envKey}="${safeValue}"`);
66
+ existing.set(finding.envKey, finding.rawValue);
67
+ written++;
68
+ }
69
+ lines.push('');
70
+ }
71
+ if (written > 0) {
72
+ fs_1.default.appendFileSync(absPath, lines.join('\n'), 'utf-8');
73
+ }
74
+ return written;
75
+ }
76
+ //# sourceMappingURL=envWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envWriter.js","sourceRoot":"","sources":["../../src/env/envWriter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Sanitizes a string to be a valid env variable name:
3
+ * - Upper-case
4
+ * - Only alphanumeric and underscores
5
+ * - Cannot start with a digit
6
+ */
7
+ export declare function sanitizeEnvKey(key: string): string;
8
+ /**
9
+ * Generates a collision-safe env key from a base name and a set of already-used keys.
10
+ * Appends _2, _3, ... until unique.
11
+ */
12
+ export declare function makeUniqueKey(baseKey: string, usedKeys: Set<string>): string;
13
+ //# sourceMappingURL=keyNamer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyNamer.d.ts","sourceRoot":"","sources":["../../src/env/keyNamer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO5E"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeEnvKey = sanitizeEnvKey;
4
+ exports.makeUniqueKey = makeUniqueKey;
5
+ /**
6
+ * Sanitizes a string to be a valid env variable name:
7
+ * - Upper-case
8
+ * - Only alphanumeric and underscores
9
+ * - Cannot start with a digit
10
+ */
11
+ function sanitizeEnvKey(key) {
12
+ let sanitized = key
13
+ .toUpperCase()
14
+ .replace(/[^A-Z0-9_]/g, '_') // replace non-alphanum/underscore with _
15
+ .replace(/^[0-9]+/, '_'); // cannot start with digits
16
+ // Collapse multiple consecutive underscores
17
+ sanitized = sanitized.replace(/__+/g, '_').replace(/^_|_$/g, '');
18
+ return sanitized || 'SECRET';
19
+ }
20
+ /**
21
+ * Generates a collision-safe env key from a base name and a set of already-used keys.
22
+ * Appends _2, _3, ... until unique.
23
+ */
24
+ function makeUniqueKey(baseKey, usedKeys) {
25
+ const sanitized = sanitizeEnvKey(baseKey);
26
+ if (!usedKeys.has(sanitized))
27
+ return sanitized;
28
+ let suffix = 2;
29
+ while (usedKeys.has(`${sanitized}_${suffix}`))
30
+ suffix++;
31
+ return `${sanitized}_${suffix}`;
32
+ }
33
+ //# sourceMappingURL=keyNamer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyNamer.js","sourceRoot":"","sources":["../../src/env/keyNamer.ts"],"names":[],"mappings":";;;;AAAA;;;;;GAKG;AACH,wBAA+B,GAAW;IACxC,IAAI,SAAS,GAAG,GAAG;SAChB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAG,yCAAyC;SACvE,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAO,2BAA2B;IAE7D,4CAA4C;IAC5C,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEjE,OAAO,SAAS,IAAI,QAAQ,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,uBAA8B,OAAe,EAAE,QAAqB;IAClE,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAE/C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC;QAAE,MAAM,EAAE,CAAC;IACxD,OAAO,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { ScanSummary } from '../types';
2
+ /**
3
+ * Shows a summary of planned changes and asks the user to confirm before proceeding.
4
+ * Returns true if the user approves, false if they decline.
5
+ */
6
+ export declare function confirmChanges(summary: ScanSummary, noRewrite: boolean): Promise<boolean>;
7
+ /**
8
+ * Asks the user to confirm backup before proceeding.
9
+ * Returns 'backup', 'skip', or 'abort'.
10
+ */
11
+ export declare function askBackupPreference(): Promise<'backup' | 'skip' | 'abort'>;
12
+ /**
13
+ * Prompts for a custom .env file path override.
14
+ */
15
+ export declare function askEnvFilePath(defaultPath: string): Promise<string>;
16
+ //# sourceMappingURL=confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../src/interactive/confirm.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,WAAW,EAAE,MAAM,UAAU,CAAC;AAEhD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAmC/F;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,CAUhF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMzE"}