fouad-env-guardian 0.1.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/README.md +61 -0
- package/cli/index.mjs +50 -0
- package/core/scan-env.mjs +82 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# `fouad-env-guardian`
|
|
2
|
+
|
|
3
|
+
CLI tool to scan projects for unsafe `.env` practices and likely secret exposure mistakes.
|
|
4
|
+
|
|
5
|
+
## Package and command
|
|
6
|
+
|
|
7
|
+
- npm package: `fouad-env-guardian`
|
|
8
|
+
- CLI command: `env-guardian`
|
|
9
|
+
|
|
10
|
+
## Install from npm
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g fouad-env-guardian
|
|
14
|
+
env-guardian
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
env-guardian
|
|
21
|
+
env-guardian --version
|
|
22
|
+
env-guardian --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What it checks
|
|
26
|
+
|
|
27
|
+
- Whether `.env` exists
|
|
28
|
+
- Whether `.env` is tracked by git
|
|
29
|
+
- Whether `.env.example` exists
|
|
30
|
+
- Whether `.env` contains common secret-like variable names such as `API_KEY`, `SECRET`, `TOKEN`, `PASSWORD`, or `PRIVATE_KEY`
|
|
31
|
+
|
|
32
|
+
## Example output
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Env Guardian Report
|
|
36
|
+
|
|
37
|
+
Project: C:\my-project
|
|
38
|
+
- .env exists: yes
|
|
39
|
+
- .env tracked by git: yes
|
|
40
|
+
- .env.example exists: no
|
|
41
|
+
- secret-like keys found: API_KEY, TOKEN
|
|
42
|
+
|
|
43
|
+
Warnings:
|
|
44
|
+
- The .env file is tracked by git.
|
|
45
|
+
- Missing .env.example.
|
|
46
|
+
- Potential secret variable detected: API_KEY
|
|
47
|
+
- Potential secret variable detected: TOKEN
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
- Run the command from the project root you want to inspect.
|
|
53
|
+
- The tool exits with a non-zero code when warnings are found, which makes it useful in CI.
|
|
54
|
+
- Detection is intentionally conservative and should be treated as a warning signal, not proof of a leak.
|
|
55
|
+
|
|
56
|
+
## Roadmap
|
|
57
|
+
|
|
58
|
+
- Auto-fix suggestions for `.gitignore`
|
|
59
|
+
- Framework-aware checks for Next.js, Vite, and similar tools
|
|
60
|
+
- Configurable secret patterns and ignore rules
|
|
61
|
+
- CI examples and GitHub Actions integration
|
package/cli/index.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { scanEnv } from "../core/scan-env.mjs";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { version } = require("../package.json");
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
12
|
+
console.log(chalk.bold("env-guardian"));
|
|
13
|
+
console.log("Scan the current project for unsafe .env practices.\n");
|
|
14
|
+
console.log(chalk.gray("Usage:"));
|
|
15
|
+
console.log(" env-guardian");
|
|
16
|
+
console.log(" env-guardian --version");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
21
|
+
console.log(version);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = scanEnv();
|
|
27
|
+
|
|
28
|
+
console.log(chalk.cyan("\nEnv Guardian Report\n"));
|
|
29
|
+
console.log(`Project: ${result.projectRoot}`);
|
|
30
|
+
console.log(`- .env exists: ${result.hasEnv ? "yes" : "no"}`);
|
|
31
|
+
console.log(`- .env tracked by git: ${result.envTracked ? "yes" : "no"}`);
|
|
32
|
+
console.log(`- .env.example exists: ${result.hasExample ? "yes" : "no"}`);
|
|
33
|
+
|
|
34
|
+
if (result.detectedSecrets.length > 0) {
|
|
35
|
+
console.log(`- secret-like keys found: ${result.detectedSecrets.join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (result.warnings.length > 0) {
|
|
39
|
+
console.log(chalk.yellow("\nWarnings:"));
|
|
40
|
+
for (const warning of result.warnings) {
|
|
41
|
+
console.log(chalk.yellow(`- ${warning}`));
|
|
42
|
+
}
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
} else {
|
|
45
|
+
console.log(chalk.green("\nNo issues found"));
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(chalk.red(err?.message || String(err)));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_SECRET_PATTERNS = [
|
|
6
|
+
{ name: "API_KEY", regex: /^\s*[A-Z0-9_]*API[_-]?KEY\s*=/im },
|
|
7
|
+
{ name: "SECRET", regex: /^\s*[A-Z0-9_]*SECRET\s*=/im },
|
|
8
|
+
{ name: "TOKEN", regex: /^\s*[A-Z0-9_]*TOKEN\s*=/im },
|
|
9
|
+
{ name: "PASSWORD", regex: /^\s*[A-Z0-9_]*PASSWORD\s*=/im },
|
|
10
|
+
{ name: "PRIVATE_KEY", regex: /^\s*[A-Z0-9_]*PRIVATE[_-]?KEY\s*=/im },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function isGitTracked(projectRoot, relativePath) {
|
|
14
|
+
try {
|
|
15
|
+
const result = execFileSync("git", ["ls-files", "--cached", "--full-name", relativePath], {
|
|
16
|
+
cwd: projectRoot,
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return result.includes(relativePath);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const stderr = String(error?.stderr || "");
|
|
24
|
+
if (
|
|
25
|
+
stderr.includes("not a git repository") ||
|
|
26
|
+
stderr.includes("fatal: not a git repository") ||
|
|
27
|
+
stderr.includes("did not match any files")
|
|
28
|
+
) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function detectSecrets(content) {
|
|
37
|
+
const findings = [];
|
|
38
|
+
|
|
39
|
+
for (const pattern of DEFAULT_SECRET_PATTERNS) {
|
|
40
|
+
if (pattern.regex.test(content)) {
|
|
41
|
+
findings.push(pattern.name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return findings;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function scanEnv(projectRoot = process.cwd(), options = {}) {
|
|
49
|
+
const gitTrackedChecker = options.gitTrackedChecker || isGitTracked;
|
|
50
|
+
const envPath = path.join(projectRoot, ".env");
|
|
51
|
+
const examplePath = path.join(projectRoot, ".env.example");
|
|
52
|
+
|
|
53
|
+
const result = {
|
|
54
|
+
projectRoot,
|
|
55
|
+
hasEnv: fs.existsSync(envPath),
|
|
56
|
+
envTracked: false,
|
|
57
|
+
hasExample: fs.existsSync(examplePath),
|
|
58
|
+
warnings: [],
|
|
59
|
+
detectedSecrets: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (result.hasEnv) {
|
|
63
|
+
result.envTracked = gitTrackedChecker(projectRoot, ".env");
|
|
64
|
+
|
|
65
|
+
if (result.envTracked) {
|
|
66
|
+
result.warnings.push("The .env file is tracked by git.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = fs.readFileSync(envPath, "utf8");
|
|
70
|
+
result.detectedSecrets = detectSecrets(content);
|
|
71
|
+
|
|
72
|
+
for (const finding of result.detectedSecrets) {
|
|
73
|
+
result.warnings.push(`Potential secret variable detected: ${finding}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!result.hasExample) {
|
|
78
|
+
result.warnings.push("Missing .env.example.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fouad-env-guardian",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI tool to scan projects for unsafe .env practices and likely secret exposure mistakes.",
|
|
5
|
+
"author": "Fouad",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"env",
|
|
10
|
+
"dotenv",
|
|
11
|
+
"security",
|
|
12
|
+
"secrets",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/FouadBechar/NextApp.git",
|
|
18
|
+
"directory": "tools/env-guardian"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/FouadBechar/NextApp/tree/main/tools/env-guardian",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/FouadBechar/NextApp/issues"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"cli",
|
|
29
|
+
"core",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"env-guardian": "cli/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"help": "node ./cli/index.mjs --help",
|
|
37
|
+
"test": "node ./tests/run-tests.mjs"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"chalk": "^5.6.2"
|
|
44
|
+
}
|
|
45
|
+
}
|