fix(api): block open redirects, add health check and body limits, run as non-root
- safeReturnTo resolves the target against a placeholder origin and rejects backslashes and control characters, so `/\evil.example` and similar payloads can no longer redirect off-site after sign-in. - Add a real unauthenticated /api/health and answer unknown /api/* paths with a JSON 404 instead of the SPA shell. - Limit request bodies: 16 KB for /api/auth/* (read before a session exists), 128 MB for resource create/update and uploads, 10 MB elsewhere. - Run the production image as an unprivileged user and add a Docker HEALTHCHECK against /api/health.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import "dotenv/config";
|
||||
import { Hono } from "hono";
|
||||
import { serveStatic } from "hono/bun";
|
||||
import { bodyLimit } from "hono/body-limit";
|
||||
import { cors } from "hono/cors";
|
||||
import { logger } from "hono/logger";
|
||||
import {authConfig, authMiddleware, authRoutes} from './auth';
|
||||
@@ -44,9 +45,19 @@ app.use(
|
||||
}),
|
||||
);
|
||||
app.use("*", logger());
|
||||
// Sign-in endpoints read the body before a session exists, so they get a small
|
||||
// limit; uploads (S3 objects, Lambda ZIPs sent as base64, Lightsail bundles) keep
|
||||
// Bun's default 128 MB ceiling.
|
||||
app.use("/api/*", (c, next) =>
|
||||
bodyLimit({
|
||||
maxSize: requestBodyLimit(c.req.path),
|
||||
onError: (c) => c.json({ error: "Request body is too large" }, 413),
|
||||
})(c, next),
|
||||
);
|
||||
app.use("*", authMiddleware(authentication));
|
||||
app.use("*", authRoutes(authentication));
|
||||
|
||||
app.get("/api/health", (c) => c.json({ status: "ok" }));
|
||||
app.route("/api/eks", eks);
|
||||
app.route("/api/ec2", ec2);
|
||||
app.route("/api/secretsmanager", secretsmanager);
|
||||
@@ -55,6 +66,7 @@ app.route("/api/msk", msk);
|
||||
app.route("/api/msk-connect", mskConnect);
|
||||
app.route("/api/ses", ses);
|
||||
app.route("/api/elasticache", elasticache);
|
||||
app.all("/api/*", (c) => c.json({ error: "Not found" }, 404));
|
||||
|
||||
// Serve static frontend files when public/ directory is present (production)
|
||||
app.use("*", serveStatic({ root: "./public" }));
|
||||
@@ -63,6 +75,16 @@ app.get("*", serveStatic({ path: "./public/index.html" }));
|
||||
const port = Number(process.env.PORT ?? 4501);
|
||||
export default { port, fetch: app.fetch };
|
||||
|
||||
function requestBodyLimit(path: string): number {
|
||||
if (path.startsWith("/api/auth/")) return 16 * 1024;
|
||||
if (
|
||||
/^\/api\/clouds\/[^/]+\/services\/[^/]+\/resources(?:\/[^/]+(?:\/object|\/static-files|\/node-application)?)?$/.test(path)
|
||||
) {
|
||||
return 128 * 1024 * 1024;
|
||||
}
|
||||
return 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
function isSensitiveCredentialRoute(path: string): boolean {
|
||||
return (
|
||||
path.startsWith("/api/secretsmanager") ||
|
||||
|
||||
Reference in New Issue
Block a user