mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-05-23 04:28:06 +00:00
Fixes the 4 fixable alerts opened in the recent scan and adds enforceable guardrails so future development follows the same pattern. Code fixes: - src/mitm/cert/install.ts: pass certPath/certName/action via exec()'s env option instead of string-interpolating them into the bash script (CodeQL js/shell-command-injection-from-environment #225) - scripts/docs/{gen-provider-reference,add-frontmatter,fix-internal-links}: escape backslash before other regex/markdown metacharacters (CodeQL js/incomplete-sanitization #227, #228, #229) Documentation (mandatory patterns): - docs/security/PUBLIC_CREDS.md — embedding public upstream OAuth/Firebase identifiers via resolvePublicCred(); never as string literals - docs/security/ERROR_SANITIZATION.md — routing every error response through sanitizeErrorMessage()/buildErrorBody(); never raw err.stack/err.message - CLAUDE.md: 4 new Hard Rules (#11-#14) + Security section + scenario notes - AGENTS.md, CONTRIBUTING.md: cross-reference the two new docs - SECURITY.md: extended Hard Security Rules with the new mandatory patterns - docs/README.md: index entries pointing to the two new docs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
148 lines
4.2 KiB
JavaScript
148 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* add-frontmatter.mjs — one-shot helper that ensures every documentation
|
||
* file under docs/<sub>/*.md and docs/README.md has a YAML frontmatter
|
||
* header with `title`, `version`, and `lastUpdated`.
|
||
*
|
||
* Idempotent: docs that already have a `---` block at the top are skipped
|
||
* (the existing frontmatter is preserved as-is). Files without a leading
|
||
* `# Title` heading fall back to the basename humanized as a title.
|
||
*
|
||
* Excludes: docs/i18n/, docs/screenshots/, docs/superpowers/,
|
||
* docs/diagrams/exported/. Subfolder READMEs are included.
|
||
*
|
||
* Usage: node scripts/docs/add-frontmatter.mjs [--version X.Y.Z] [--date YYYY-MM-DD]
|
||
*/
|
||
|
||
import { promises as fs } from "node:fs";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
const ROOT = path.resolve(__dirname, "..", "..");
|
||
const DOCS_DIR = path.join(ROOT, "docs");
|
||
|
||
const EXCLUDE_PREFIXES = [
|
||
"docs/i18n/",
|
||
"docs/screenshots/",
|
||
"docs/superpowers/",
|
||
"docs/diagrams/exported/",
|
||
];
|
||
|
||
const args = process.argv.slice(2);
|
||
let version = "3.8.0";
|
||
let lastUpdated = "2026-05-13";
|
||
for (let i = 0; i < args.length; i += 1) {
|
||
if (args[i] === "--version" && args[i + 1]) {
|
||
version = args[i + 1];
|
||
i += 1;
|
||
} else if (args[i] === "--date" && args[i + 1]) {
|
||
lastUpdated = args[i + 1];
|
||
i += 1;
|
||
}
|
||
}
|
||
|
||
async function walk(dir, files = []) {
|
||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||
for (const entry of entries) {
|
||
const full = path.join(dir, entry.name);
|
||
if (entry.isDirectory()) {
|
||
await walk(full, files);
|
||
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
||
files.push(full);
|
||
}
|
||
}
|
||
return files;
|
||
}
|
||
|
||
function isExcluded(rel) {
|
||
return EXCLUDE_PREFIXES.some((p) => rel === p || rel.startsWith(p));
|
||
}
|
||
|
||
function hasFrontmatter(content) {
|
||
return /^---\r?\n/.test(content);
|
||
}
|
||
|
||
function extractTopHeading(content) {
|
||
const lines = content.replace(/^/, "").split(/\r?\n/);
|
||
for (const line of lines) {
|
||
const m = line.match(/^#\s+(.+?)\s*$/);
|
||
if (m) return m[1].trim();
|
||
// Allow blank/HTML-comment lines before the first heading
|
||
if (line.trim() === "" || /^<!--/.test(line.trim())) continue;
|
||
// Anything else (e.g. badge image, paragraph) — no usable heading
|
||
return null;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function humanizeBasename(filePath) {
|
||
const base = path.basename(filePath, ".md");
|
||
if (base.toLowerCase() === "readme") {
|
||
const parent = path.basename(path.dirname(filePath));
|
||
if (parent && parent !== "." && parent !== "docs") {
|
||
const cap = parent.charAt(0).toUpperCase() + parent.slice(1);
|
||
return `${cap} Docs`;
|
||
}
|
||
return "Documentation";
|
||
}
|
||
return base
|
||
.replace(/[-_]+/g, " ")
|
||
.replace(/\s+/g, " ")
|
||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||
}
|
||
|
||
function buildFrontmatter(title) {
|
||
// Quote title with double quotes; escape backslashes first, then double quotes.
|
||
const safe = title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
||
return [
|
||
`---`,
|
||
`title: "${safe}"`,
|
||
`version: ${version}`,
|
||
`lastUpdated: ${lastUpdated}`,
|
||
`---`,
|
||
``,
|
||
].join("\n");
|
||
}
|
||
|
||
async function main() {
|
||
const allFiles = await walk(DOCS_DIR);
|
||
const targets = allFiles
|
||
.map((abs) => ({ abs, rel: path.relative(ROOT, abs).replace(/\\/g, "/") }))
|
||
.filter(({ rel }) => !isExcluded(rel));
|
||
|
||
let added = 0;
|
||
let skipped = 0;
|
||
let noHeading = [];
|
||
|
||
for (const { abs, rel } of targets) {
|
||
const content = await fs.readFile(abs, "utf8");
|
||
if (hasFrontmatter(content)) {
|
||
skipped += 1;
|
||
continue;
|
||
}
|
||
let title = extractTopHeading(content);
|
||
if (!title) {
|
||
title = humanizeBasename(abs);
|
||
noHeading.push(rel);
|
||
}
|
||
const fm = buildFrontmatter(title);
|
||
const next = `${fm}\n${content.replace(/^/, "")}`;
|
||
await fs.writeFile(abs, next, "utf8");
|
||
added += 1;
|
||
console.log(`[add-frontmatter] added → ${rel}`);
|
||
}
|
||
|
||
console.log(`[add-frontmatter] done — added=${added} skipped=${skipped} total=${targets.length}`);
|
||
if (noHeading.length > 0) {
|
||
console.log(
|
||
`[add-frontmatter] fallback title used (no leading H1) for: ${noHeading.join(", ")}`
|
||
);
|
||
}
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error(err);
|
||
process.exit(1);
|
||
});
|