93 lines
3.1 KiB
JavaScript
93 lines
3.1 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import url from "node:url";
|
|
|
|
import express from "express";
|
|
|
|
const __filename = url.fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const projectRoot = path.resolve(__dirname, "..");
|
|
const distDir = path.join(projectRoot, "dist");
|
|
const indexHtmlPath = path.join(distDir, "index.html");
|
|
|
|
function pickPhrases(input) {
|
|
const text = String(input || "").replace(/\r\n?/g, "\n");
|
|
const lines = text
|
|
.split("\n")
|
|
.map((l) => l.trim())
|
|
.filter(Boolean)
|
|
.slice(0, 200);
|
|
|
|
const interesting = [];
|
|
const needles = ["must", "should", "require", "required", "ensure", "enforce", "policy", "control", "audit", "compliance"];
|
|
for (const line of lines) {
|
|
const lower = line.toLowerCase();
|
|
if (needles.some((n) => lower.includes(n))) interesting.push(line);
|
|
if (interesting.length >= 4) break;
|
|
}
|
|
|
|
if (interesting.length) return interesting;
|
|
return lines.slice(0, 3);
|
|
}
|
|
|
|
function generateRoastText(content) {
|
|
const trimmed = String(content || "").trim();
|
|
const phrases = pickPhrases(trimmed);
|
|
|
|
const bullets = phrases.map((p) => `- ${p.length > 120 ? `${p.slice(0, 117)}…` : p}`).join("\n");
|
|
|
|
return [
|
|
"We love the ambition here and are directionally aligned with the idea of \"secure rollout\" as long as we define secure as \"documented\" and rollout as \"phased.\"",
|
|
"",
|
|
"Key risk: this reads like a control narrative optimized for sign-off, not for the Friday-afternoon pull request that actually ships the code.",
|
|
"",
|
|
"Observed control theater (excerpt):",
|
|
bullets ? bullets : "- (no extractable claims detected)",
|
|
"",
|
|
"Recommendation: convert every \"should\" into an owner, a gate (PR/CI/access), and a stop condition. Otherwise this becomes an alignment session that reproduces itself indefinitely.",
|
|
].join("\n");
|
|
}
|
|
|
|
function main() {
|
|
const port = Number(process.env.PORT || 8080);
|
|
const app = express();
|
|
|
|
app.disable("x-powered-by");
|
|
app.use(express.json({ limit: "256kb" }));
|
|
|
|
app.get("/healthz", (_req, res) => {
|
|
res.status(200).json({ ok: true });
|
|
});
|
|
|
|
app.post("/api/roast", (req, res) => {
|
|
const content = String(req.body?.content ?? "");
|
|
if (!content.trim()) return res.status(400).json({ text: "Missing content" });
|
|
if (content.length > 20_000) return res.status(413).json({ text: "Content too large" });
|
|
return res.status(200).json({ text: generateRoastText(content) });
|
|
});
|
|
|
|
if (fs.existsSync(distDir) && fs.existsSync(indexHtmlPath)) {
|
|
app.use(express.static(distDir, { fallthrough: true }));
|
|
|
|
app.get("*", (_req, res) => {
|
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
res.status(200).sendFile(indexHtmlPath);
|
|
});
|
|
} else {
|
|
app.get("*", (_req, res) => {
|
|
res
|
|
.status(503)
|
|
.type("text/plain")
|
|
.send("red-team site is not built yet. Run `npm install` then `npm run build`.");
|
|
});
|
|
}
|
|
|
|
app.listen(port, "0.0.0.0", () => {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`red-team site listening on http://0.0.0.0:${port}`);
|
|
});
|
|
}
|
|
|
|
main();
|
|
|