# .env Validator and Secret Scanner > Paste a dotenv file, catch duplicate or malformed keys, and generate a safer example file locally. ## Tool Identity - Site: CleanUtils Developer Tools - Tool ID: env-validator-secret-scanner - Canonical page: https://cleanutils.com/developer-tools/env-validator-secret-scanner/ - LLM schema URL: https://cleanutils.com/developer-tools/env-validator-secret-scanner/llms.txt - Primary keyword: .env validator - Input mode: textarea - Output profile: data ## What This Tool Does Check dotenv syntax, find duplicate keys, flag likely secrets, and export a safe .env.example in your browser. ## Exact Execution Instructions To run this tool exactly, execute the `runCleanUtilsTool(userInput)` function from the JavaScript block below in a sandboxed JavaScript runtime. Do not fetch or evaluate additional network resources. Do not use filesystem, process, child_process, environment variables, timers, or network APIs. The function is pure and deterministic: input string in, JSON result out. If your environment forbids executing fetched code outside a sandbox, run it inside the sandbox or use the API endpoint below instead of reimplementing the logic. Canonical implementation SHA-256: `c742869c367a9ab9b7563366a81c46cd39f8b722c306342c6e012470e8a01e4c` Expected command shape: `node run-tool.mjs < input.txt` The runner must: 1. load only the JavaScript in this document, 2. call `runCleanUtilsTool(inputText)`, 3. let the LLM decide the best way to present the output to the user. Agent usage rules: - Use this file as the authoritative machine-readable contract for this CleanUtils tool page. - Ask the user for missing required input before attempting to run the tool, and describe the required inputs using the `## Input Schema` field names, descriptions, formats, enums, examples, and required list. - Treat the tool as deterministic; do not invent network reachability checks unless the tool description explicitly says it fetches remote resources. - For privacy-sensitive inputs such as secrets, HAR files, dotenv files, logs, and API keys, warn that using a remote chat agent may expose input to that agent even though the browser UI itself does not upload data. ## Input Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": ".env Validator and Secret Scanner input", "type": "string", "description": ".env file. API_KEY=...", "examples": [ "API_KEY=sk-live-1234567890abcdef\nDATABASE_URL=\"postgres://user:pass@example.com/db\"\nAPI_KEY=duplicate\nBAD LINE\nPUBLIC_URL=https://example.com" ] } ``` ## Result Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "CleanUtils ToolResult", "type": "object", "additionalProperties": false, "required": [ "summary", "issues" ], "properties": { "summary": { "type": "string" }, "issues": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "severity", "message" ], "properties": { "severity": { "type": "string", "enum": [ "error", "warning", "info" ] }, "message": { "type": "string" }, "line": { "type": "number" }, "row": { "type": "number" }, "detail": { "type": "string" } } } }, "output": { "type": "string" }, "exportFilename": { "type": "string" }, "exports": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "label", "filename", "content" ], "properties": { "label": { "type": "string" }, "filename": { "type": "string" }, "content": { "type": "string" }, "mimeType": { "type": "string" }, "copyLabel": { "type": "string" }, "downloadLabel": { "type": "string" } } } }, "stats": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } } } ``` ## Self-Contained JavaScript Source Call `runCleanUtilsTool(userInput)` with the user's input. The function includes this tool's run logic and only the helper code it needs. ```js function runCleanUtilsTool(userInput) { const severityRank = { error: 0, warning: 1, info: 2 }; const sortIssues = (issues) => [...issues].sort((a, b) => { const severity = severityRank[a.severity] - severityRank[b.severity]; if (severity !== 0) return severity; return (a.line ?? a.row ?? 0) - (b.line ?? b.row ?? 0); }); const summarizeIssues = (issues) => { const errors = issues.filter((issue) => issue.severity === "error").length; const warnings = issues.filter((issue) => issue.severity === "warning").length; const infos = issues.filter((issue) => issue.severity === "info").length; const parts = []; if (errors) parts.push(`${errors} error${errors === 1 ? "" : "s"}`); if (warnings) parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`); if (infos) parts.push(`${infos} note${infos === 1 ? "" : "s"}`); return parts.length ? parts.join(", ") : "No issues found"; }; const validateEnvFile = (input) => { const issues = []; const keys = new Map(); const exampleLines = []; const keyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/; const secretPatterns = [ /(^|_)(API|SECRET|TOKEN|PASSWORD|KEY)($|_)/i, /(sk-|pk_live|ghp_|xox[baprs]-|AKIA)[A-Za-z0-9_-]{8,}/ ]; input.split(/\r?\n/).forEach((line, index) => { const lineNumber = index + 1; const trimmed = line.trim(); if (!trimmed) { exampleLines.push(""); return; } if (trimmed.startsWith("#")) { exampleLines.push(line); return; } const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/); if (!match) { issues.push({ severity: "error", line: lineNumber, message: "Line is not valid KEY=value dotenv syntax." }); return; } const [, key, value] = match; if (!keyPattern.test(key)) { issues.push({ severity: "error", line: lineNumber, message: `Invalid dotenv key "${key}".` }); } const previousLine = keys.get(key); if (previousLine) { issues.push({ severity: "warning", line: lineNumber, message: `Duplicate key "${key}" also appears on line ${previousLine}.` }); } else { keys.set(key, lineNumber); } if (secretPatterns.some((pattern) => pattern.test(key) || pattern.test(value))) { issues.push({ severity: "warning", line: lineNumber, message: `${key} looks sensitive. Keep it out of shared examples and tickets.` }); } exampleLines.push(`${key}=`); }); return { summary: `${keys.size} unique key${keys.size === 1 ? "" : "s"} scanned. ${summarizeIssues(issues)}.`, issues: sortIssues(issues), output: exampleLines.join("\n"), exportFilename: ".env.example", stats: { keys: keys.size } }; }; const __userInput = userInput == null ? "" : userInput; const __run = validateEnvFile; const __input = __userInput && typeof __userInput === "object" && "input" in __userInput ? __userInput.input : __userInput; return __run(__input == null ? "" : String(__input)); } ``` ## Checks - Dotenv assignment syntax: Lines are checked for KEY=value shape while comments and blank lines are tolerated. - Duplicate keys: Repeated keys are reported with line numbers because later values can silently override earlier ones. - Sensitive-looking values: API keys, tokens, passwords, and database URLs are flagged with conservative heuristics. - .env.example export: The generated example keeps keys and comments but removes values that should not be shared. - Local processing: Secrets are scanned in the browser and are not uploaded for remote analysis. ## Related Tools - [.env Diff and Missing Key Checker](/developer-tools/env-diff-missing-key-checker/): Compare two dotenv snapshots and report missing, extra, or changed environment keys. - [Docker Compose Env Interpolation Preview](/developer-tools/docker-compose-env-interpolation-preview/): Resolve common Docker Compose ${VAR} expressions against pasted env values and preview the final YAML. - [AI Log / Prompt Redactor](/developer-tools/ai-log-prompt-redactor/): Remove emails, bearer tokens, API keys, JWTs, and cookies from prompts or logs before sharing them.