fixyoursecret 0.3.1-developer-preview.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.3.1-developer-preview.1] - 2026-03-26
6
+
7
+ ### Added
8
+ - Expanded detector coverage:
9
+ - Twilio, SendGrid, Mailgun
10
+ - Anthropic, Cohere, Hugging Face
11
+ - GitLab, Telegram, npm token detectors
12
+ - Detector registry and shared detector runner
13
+ - Benchmark corpus (`fixtures/benchmark/*`) for positive and negative samples
14
+ - Benchmark quality gate script (`scripts/benchmark.js`)
15
+ - CI benchmark threshold enforcement (recall/precision gate)
16
+ - Benchmark unit test
17
+ - Multi-repo tuning harness (`scripts/multi-repo-tune.js`)
18
+ - Weekly tuning GitHub workflow (`.github/workflows/weekly-tuning.yml`)
19
+ - False-positive review artifact generation (`docs/tuning/false-positive-review.md`)
20
+
21
+ ### Improved
22
+ - Stronger, measurable provider-detection quality process
23
+ - Release quality now validated by tests + benchmark gate
24
+
25
+ ## [0.3.0-developer-preview.1] - 2026-03-26
26
+
27
+ ### Added
28
+ - New detectors:
29
+ - AWS access key IDs
30
+ - Stripe secret keys
31
+ - Slack tokens
32
+ - GitHub tokens
33
+ - Private key block detection
34
+ - Optional verification mode (`--verify safe`) with `--verify-strict`
35
+ - First-class `history` command for commit-history scanning
36
+ - Improved false-positive controls:
37
+ - default suppressions for test/fixture contexts
38
+ - value hint ignores (`example`, `dummy`, `fake`, etc.)
39
+
40
+ ### Changed
41
+ - Rebranded CLI to `fixyoursecret` (kept `secretlint` alias for compatibility)
42
+ - Default config/baseline filenames now:
43
+ - `.fixyoursecretrc.json`
44
+ - `.fixyoursecret-baseline.json`
45
+ - CI workflow renamed and SARIF output changed to `fixyoursecret.sarif`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Secretlint
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,223 @@
1
+ <div align="center">
2
+
3
+ # FixYourSecret
4
+
5
+ **Developer Preview**
6
+
7
+ An ESLint-style CLI that finds leaked credentials, flags frontend exposure, suggests fixes, and helps rotate keys safely.
8
+
9
+ [![Node >= 20](https://img.shields.io/badge/node-%3E%3D20-2ea44f)](https://nodejs.org/)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
11
+ [![CI](https://img.shields.io/badge/ci-github%20actions-2088ff)](https://github.com/ssanidhya0407/fixyoursecret/actions)
12
+
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## What Problem This Solves
18
+ Developers accidentally commit API keys, tokens, or private keys. That leads to abuse, unexpected costs, and incident response fire drills.
19
+
20
+ FixYourSecret helps teams catch these mistakes early and fix them with clear next steps.
21
+
22
+ ---
23
+
24
+ ## Why This Is Stronger Now
25
+ - Expanded provider detector coverage significantly.
26
+ - Added benchmark-driven quality gates (`npm run benchmark`) so quality is measured every release.
27
+ - Added CI threshold enforcement for recall/precision.
28
+ - Added optional local verification mode for higher-confidence findings.
29
+
30
+ ---
31
+
32
+ ## Detector Coverage
33
+ FixYourSecret currently includes detector coverage for:
34
+
35
+ - OpenAI
36
+ - Google
37
+ - AWS Access Key IDs
38
+ - Stripe Secret Keys
39
+ - Slack Tokens
40
+ - GitHub Tokens
41
+ - GitLab Tokens
42
+ - Twilio API Keys
43
+ - SendGrid API Keys
44
+ - Mailgun API Keys
45
+ - Anthropic API Keys
46
+ - Cohere API Keys
47
+ - Hugging Face Tokens
48
+ - Telegram Bot Tokens
49
+ - npm Tokens
50
+ - Private Key Blocks
51
+ - Generic High-Entropy Tokens
52
+
53
+ ---
54
+
55
+ ## What You Get
56
+ - Fast secret scanning with file/line/snippet output
57
+ - Frontend exposure risk highlighting
58
+ - Optional safe verification mode (`--verify safe`)
59
+ - First-class history scanning (`history`)
60
+ - Better false-positive controls (hints + suppressions + defaults)
61
+ - Baseline support for gradual rollout
62
+ - SARIF output for CI/security platforms
63
+ - Template-based fix generation (`fix`)
64
+ - Guided key rotation (`rotate`)
65
+
66
+ ---
67
+
68
+ ## Install
69
+ ```bash
70
+ npm install
71
+ npm test
72
+ npm link
73
+ ```
74
+
75
+ You can run either command name (compatibility included):
76
+
77
+ ```bash
78
+ fixyoursecret --help
79
+ secretlint --help
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Quick Start
85
+ ```bash
86
+ fixyoursecret init
87
+ fixyoursecret scan --verify safe
88
+ fixyoursecret history 30
89
+ fixyoursecret fix
90
+ fixyoursecret rotate openai --dry-run
91
+ fixyoursecret hook install
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Quality and Benchmarks
97
+ Run quality checks locally:
98
+
99
+ ```bash
100
+ npm run quality
101
+ ```
102
+
103
+ Run benchmark only:
104
+
105
+ ```bash
106
+ npm run benchmark
107
+ ```
108
+
109
+ Run multi-repo tuning report:
110
+
111
+ ```bash
112
+ npm run tune:multi
113
+ ```
114
+
115
+ CI quality gate thresholds (defaults):
116
+ - Recall >= 0.95
117
+ - Precision >= 0.95
118
+
119
+ These can be tuned via env vars:
120
+ - `BENCH_MIN_RECALL`
121
+ - `BENCH_MIN_PRECISION`
122
+
123
+ Tuning workflow docs:
124
+ - [./docs/tuning/process.md](./docs/tuning/process.md)
125
+
126
+ ---
127
+
128
+ ## Command Cheat Sheet
129
+ | Command | Purpose | Example |
130
+ |---|---|---|
131
+ | `fixyoursecret init` | Create default config and baseline files | `fixyoursecret init --force` |
132
+ | `fixyoursecret scan` | Scan current working tree | `fixyoursecret scan --verify safe` |
133
+ | `fixyoursecret history <n>` | Scan files touched in last `n` commits | `fixyoursecret history 50 --verify safe` |
134
+ | `fixyoursecret ci` | CI-focused SARIF scan | `fixyoursecret ci --output-file fixyoursecret.sarif` |
135
+ | `fixyoursecret fix` | Generate backend proxy + frontend patch helper | `fixyoursecret fix --output fixyoursecret-output` |
136
+ | `fixyoursecret rotate <provider>` | Rotate and update env safely | `fixyoursecret rotate openai --dry-run` |
137
+ | `fixyoursecret hook install` | Install pre-commit secret scan hook | `fixyoursecret hook install` |
138
+
139
+ ---
140
+
141
+ ## Scan Options
142
+ ```bash
143
+ fixyoursecret scan [options]
144
+ ```
145
+
146
+ - `--format text|json|sarif`
147
+ - `--output-file <path>`
148
+ - `--fail-on low|medium|high`
149
+ - `--config <path>`
150
+ - `--verify none|safe`
151
+ - `--verify-strict`
152
+ - `--staged`
153
+ - `--tracked`
154
+ - `--history <n>`
155
+ - `--baseline <path>`
156
+ - `--update-baseline`
157
+ - `--no-baseline`
158
+
159
+ ---
160
+
161
+ ## Verification Mode (Optional)
162
+ `--verify safe` performs local structure checks for supported detectors (no external API calls).
163
+
164
+ Use `--verify-strict` to drop findings that fail verification.
165
+
166
+ ---
167
+
168
+ ## Config (`.fixyoursecretrc.json`)
169
+ ```json
170
+ {
171
+ "ignorePaths": ["node_modules/**", ".git/**", "dist/**", "build/**", ".next/**", "coverage/**"],
172
+ "allowedExtensions": [".js", ".ts", ".jsx", ".tsx", ".env", ".swift"],
173
+ "maxFileSizeKB": 256,
174
+ "entropyThreshold": 3.8,
175
+ "failOn": "high",
176
+ "verifyMode": "none",
177
+ "ignoreDetectors": [],
178
+ "ignoreValueHints": ["example", "dummy", "fake", "sample", "replace_in_runtime_only"],
179
+ "suppressions": [
180
+ { "path": "test/" },
181
+ { "path": "tests/" },
182
+ { "path": "__tests__/" },
183
+ { "path": "fixtures/" }
184
+ ]
185
+ }
186
+ ```
187
+
188
+ Inline suppression comments supported:
189
+
190
+ ```js
191
+ // fixyoursecret-disable-next-line
192
+ const token = "fake_token_for_docs_only";
193
+ ```
194
+
195
+ ---
196
+
197
+ ## CI Integration
198
+ Workflow file included:
199
+
200
+ - [./.github/workflows/fixyoursecret-ci.yml](./.github/workflows/fixyoursecret-ci.yml)
201
+
202
+ It runs tests, benchmark gate, scan, and uploads SARIF.
203
+
204
+ ---
205
+
206
+ ## Publish
207
+ ```bash
208
+ npm ci
209
+ npm run quality
210
+ npm pack --dry-run
211
+ npm publish --access public
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Notes
217
+ - Existing users of `secretlint` command are still supported via alias.
218
+ - Brand chosen to avoid naming collision with existing Secretlint ecosystem tooling.
219
+
220
+ ---
221
+
222
+ ## License
223
+ MIT. See [LICENSE](./LICENSE).
package/bin/index.js ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { runScan } from "../commands/scan.js";
4
+ import { runFix } from "../commands/fix.js";
5
+ import { runRotate } from "../commands/rotate.js";
6
+ import { runInit } from "../commands/init.js";
7
+ import { runHookInstall } from "../commands/hook.js";
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name("fixyoursecret")
13
+ .description("Developer-first CLI for finding leaked secrets and safely fixing exposure")
14
+ .version("0.3.0-developer-preview.1");
15
+
16
+ program
17
+ .command("init")
18
+ .description("Initialize .fixyoursecretrc.json and baseline file")
19
+ .option("-p, --path <path>", "project path", process.cwd())
20
+ .option("--force", "overwrite existing files", false)
21
+ .action(async (options) => {
22
+ process.exitCode = await runInit(options);
23
+ });
24
+
25
+ program
26
+ .command("scan")
27
+ .description("Scan project files for leaked API keys and exposure risks")
28
+ .option("-p, --path <path>", "project path to scan", process.cwd())
29
+ .option("--json", "output findings as JSON", false)
30
+ .option("--format <format>", "output format: text|json|sarif", "text")
31
+ .option("--output-file <path>", "write JSON/SARIF output to file")
32
+ .option("--fail-on <severity>", "low|medium|high", "high")
33
+ .option("--config <path>", "custom config file path")
34
+ .option("--verify <mode>", "verification mode: none|safe", "none")
35
+ .option("--verify-strict", "drop findings that fail selected verification mode", false)
36
+ .option("--staged", "scan only staged git files", false)
37
+ .option("--tracked", "scan tracked git files", false)
38
+ .option("--history <n>", "scan files touched in last n commits")
39
+ .option("--baseline <path>", "baseline file path", ".fixyoursecret-baseline.json")
40
+ .option("--update-baseline", "replace baseline with current findings", false)
41
+ .option("--no-baseline", "ignore baseline filtering")
42
+ .action(async (options) => {
43
+ process.exitCode = await runScan(options);
44
+ });
45
+
46
+ program
47
+ .command("history")
48
+ .description("First-class history scan (defaults to last 20 commits)")
49
+ .argument("[commits]", "number of commits to inspect", "20")
50
+ .option("-p, --path <path>", "project path to scan", process.cwd())
51
+ .option("--format <format>", "output format: text|json|sarif", "text")
52
+ .option("--output-file <path>", "write JSON/SARIF output to file")
53
+ .option("--fail-on <severity>", "low|medium|high", "high")
54
+ .option("--config <path>", "custom config file path")
55
+ .option("--verify <mode>", "verification mode: none|safe", "none")
56
+ .option("--verify-strict", "drop findings that fail selected verification mode", false)
57
+ .action(async (commits, options) => {
58
+ process.exitCode = await runScan({
59
+ ...options,
60
+ history: commits,
61
+ });
62
+ });
63
+
64
+ program
65
+ .command("fix")
66
+ .description("Generate backend proxy + frontend patch template for exposed API calls")
67
+ .option("-p, --path <path>", "project path to inspect", process.cwd())
68
+ .option("-o, --output <path>", "output folder", "fixyoursecret-output")
69
+ .action(async (options) => {
70
+ process.exitCode = await runFix(options);
71
+ });
72
+
73
+ program
74
+ .command("ci")
75
+ .description("CI-focused scan (SARIF output + fail on medium by default)")
76
+ .option("-p, --path <path>", "project path to scan", process.cwd())
77
+ .option("--output-file <path>", "sarif output path", "fixyoursecret.sarif")
78
+ .option("--fail-on <severity>", "low|medium|high", "medium")
79
+ .option("--config <path>", "custom config file path")
80
+ .option("--verify <mode>", "verification mode: none|safe", "safe")
81
+ .option("--verify-strict", "drop findings that fail selected verification mode", false)
82
+ .action(async (options) => {
83
+ process.exitCode = await runScan({
84
+ path: options.path,
85
+ format: "sarif",
86
+ outputFile: options.outputFile,
87
+ failOn: options.failOn,
88
+ config: options.config,
89
+ verify: options.verify,
90
+ verifyStrict: options.verifyStrict,
91
+ tracked: true,
92
+ });
93
+ });
94
+
95
+ program
96
+ .command("rotate")
97
+ .description("Rotate API keys safely and update .env")
98
+ .argument("<provider>", "provider name e.g. openai")
99
+ .option("-p, --path <path>", "project path", process.cwd())
100
+ .option("--env-file <path>", "explicit .env file path")
101
+ .option("--dry-run", "validate and preview without writing files", false)
102
+ .option("--key <value>", "non-interactive key value (use with caution)")
103
+ .action(async (provider, options) => {
104
+ process.exitCode = await runRotate(provider, options);
105
+ });
106
+
107
+ const hook = program.command("hook").description("Manage git hooks");
108
+ hook
109
+ .command("install")
110
+ .description("Install a pre-commit hook to block high-risk secrets")
111
+ .option("-p, --path <path>", "project path", process.cwd())
112
+ .action(async (options) => {
113
+ process.exitCode = await runHookInstall(options);
114
+ });
115
+
116
+ program.parseAsync(process.argv);
@@ -0,0 +1,88 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { collectProjectFiles } from "../utils/fileScanner.js";
4
+ import { logger } from "../utils/logger.js";
5
+ import { expressProxyTemplate } from "../templates/expressProxy.js";
6
+
7
+ const EXPOSED_OPENAI_CALL_REGEX =
8
+ /fetch\s*\(\s*["'`]https:\/\/api\.openai\.com\/[\s\S]{0,500}?["'`]?Authorization["'`]?\s*:\s*["'`]Bearer\s+sk-[A-Za-z0-9_-]{20,}["'`]/;
9
+
10
+ export async function runFix(options = {}) {
11
+ const projectPath = path.resolve(options.path || process.cwd());
12
+ const outputDir = path.resolve(options.output || "fixyoursecret-output");
13
+
14
+ const files = await collectProjectFiles(projectPath);
15
+ const riskyFiles = [];
16
+
17
+ for (const file of files) {
18
+ if (!/\.(js|jsx|ts|tsx)$/i.test(file.relativePath)) continue;
19
+ if (EXPOSED_OPENAI_CALL_REGEX.test(file.content)) {
20
+ riskyFiles.push(file.relativePath);
21
+ }
22
+ }
23
+
24
+ await fs.mkdir(outputDir, { recursive: true });
25
+
26
+ const backendPath = path.join(outputDir, "backend.js");
27
+ const patchPath = path.join(outputDir, "frontend.patch.js");
28
+
29
+ await fs.writeFile(backendPath, expressProxyTemplate(), "utf8");
30
+ await fs.writeFile(patchPath, buildFrontendPatch(riskyFiles), "utf8");
31
+
32
+ logger.safe(`Generated: ${backendPath}`);
33
+ logger.safe(`Generated: ${patchPath}`);
34
+
35
+ if (riskyFiles.length > 0) {
36
+ logger.warn("Detected exposed API call patterns in:");
37
+ for (const file of riskyFiles) {
38
+ logger.log(` - ${file}`);
39
+ }
40
+ } else {
41
+ logger.warn("No explicit OpenAI fetch patterns found, templates generated as preventive defaults.");
42
+ }
43
+
44
+ logger.log("\nNext steps:");
45
+ logger.log("1. Run backend proxy server (backend.js)");
46
+ logger.log("2. Replace direct frontend provider calls with callAIProxy(...)");
47
+ logger.log("3. Move API key to OPENAI_API_KEY in backend environment");
48
+
49
+ return 0;
50
+ }
51
+
52
+ function buildFrontendPatch(riskyFiles) {
53
+ const fileList = riskyFiles.length > 0 ? riskyFiles.map((f) => `// - ${f}`).join("\n") : "// No risky files auto-detected";
54
+
55
+ return `/**
56
+ * fixyoursecret generated frontend patch helper.
57
+ * Replace direct provider calls with this internal API call.
58
+ *\n${fileList}
59
+ */
60
+
61
+ export async function callAIProxy(messages) {
62
+ const response = await fetch("/api/ai", {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ messages })
66
+ });
67
+
68
+ if (!response.ok) {
69
+ const err = await response.text();
70
+ throw new Error(
71
+ "Proxy request failed: " + err
72
+ );
73
+ }
74
+
75
+ return response.json();
76
+ }
77
+
78
+ /*
79
+ Example replacement:
80
+
81
+ Before:
82
+ fetch("https://api.openai.com/v1/chat/completions", { ... Authorization: "Bearer sk-..." ... })
83
+
84
+ After:
85
+ const data = await callAIProxy(messages)
86
+ */
87
+ `;
88
+ }
@@ -0,0 +1,37 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { logger } from "../utils/logger.js";
4
+
5
+ export async function runHookInstall(options = {}) {
6
+ const projectPath = path.resolve(options.path || process.cwd());
7
+ const hookPath = path.join(projectPath, ".git", "hooks", "pre-commit");
8
+
9
+ const script = `#!/usr/bin/env sh
10
+ # fixyoursecret pre-commit hook
11
+ if command -v fixyoursecret >/dev/null 2>&1; then
12
+ SCANNER=fixyoursecret
13
+ elif command -v secretlint >/dev/null 2>&1; then
14
+ SCANNER=secretlint
15
+ else
16
+ echo "[fixyoursecret] CLI not found in PATH"
17
+ exit 1
18
+ fi
19
+
20
+ $SCANNER scan --staged --fail-on high --verify safe
21
+ status=$?
22
+ if [ "$status" -ne 0 ]; then
23
+ echo "[fixyoursecret] commit blocked due to high-risk findings"
24
+ exit $status
25
+ fi
26
+ `;
27
+
28
+ try {
29
+ await fs.mkdir(path.dirname(hookPath), { recursive: true });
30
+ await fs.writeFile(hookPath, script, { encoding: "utf8", mode: 0o755 });
31
+ logger.safe(`Installed pre-commit hook at ${hookPath}`);
32
+ return 0;
33
+ } catch (error) {
34
+ logger.error(`Failed to install hook: ${error.message}`);
35
+ return 1;
36
+ }
37
+ }
@@ -0,0 +1,30 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { defaultConfigTemplate } from "../utils/config.js";
4
+ import { logger } from "../utils/logger.js";
5
+
6
+ export async function runInit(options = {}) {
7
+ const projectPath = path.resolve(options.path || process.cwd());
8
+ const configPath = path.join(projectPath, ".fixyoursecretrc.json");
9
+ const baselinePath = path.join(projectPath, ".fixyoursecret-baseline.json");
10
+
11
+ await writeIfMissing(configPath, defaultConfigTemplate(), Boolean(options.force));
12
+ await writeIfMissing(baselinePath, "[]\n", Boolean(options.force));
13
+
14
+ logger.safe(`Initialized ${configPath}`);
15
+ logger.safe(`Initialized ${baselinePath}`);
16
+ return 0;
17
+ }
18
+
19
+ async function writeIfMissing(filePath, content, force) {
20
+ if (force) {
21
+ await fs.writeFile(filePath, content, "utf8");
22
+ return;
23
+ }
24
+
25
+ try {
26
+ await fs.access(filePath);
27
+ } catch {
28
+ await fs.writeFile(filePath, content, "utf8");
29
+ }
30
+ }