Backend hardening: structured logging, timeouts, memory leak fixes, compression, XSS fix
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s

- Add pino structured logging with request IDs (X-Request-Id header)
- Add 30s timeout to acquirePage() and renderPdf/renderUrlPdf
- Add verification cache cleanup (every 15min) and rate limit cleanup (every 60s)
- Read version from package.json in health endpoint
- Add compression middleware
- Escape currency in templates (XSS fix)
- Add static asset caching (1h maxAge)
- Remove deprecated docker-compose version field
- Replace all console.log/error with pino logger
This commit is contained in:
OpenClaw 2026-02-16 08:27:42 +00:00
parent 4833edf44c
commit 9541ae1826
20 changed files with 319 additions and 74 deletions

View file

@ -1,6 +1,7 @@
import { Router, Request, Response } from "express";
import Stripe from "stripe";
import { createProKey, revokeByCustomer } from "../services/keys.js";
import logger from "../services/logger.js";
function escapeHtml(s: string): string {
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@ -33,7 +34,7 @@ router.post("/checkout", async (_req: Request, res: Response) => {
res.json({ url: session.url });
} catch (err: any) {
console.error("Checkout error:", err.message);
logger.error({ err }, "Checkout error");
res.status(500).json({ error: "Failed to create checkout session" });
}
});
@ -79,7 +80,7 @@ a { color: #4f9; }
<p><a href="/docs">View API docs </a></p>
</div></body></html>`);
} catch (err: any) {
console.error("Success page error:", err.message);
logger.error({ err }, "Success page error");
res.status(500).json({ error: "Failed to retrieve session" });
}
});
@ -97,7 +98,7 @@ router.post("/webhook", async (req: Request, res: Response) => {
try {
event = JSON.parse(typeof req.body === "string" ? req.body : req.body.toString()) as Stripe.Event;
} catch (err: any) {
console.error("Failed to parse webhook body:", err.message);
logger.error({ err }, "Failed to parse webhook body");
res.status(400).json({ error: "Invalid payload" });
return;
}
@ -108,7 +109,7 @@ router.post("/webhook", async (req: Request, res: Response) => {
try {
event = getStripe().webhooks.constructEvent(req.body, sig, webhookSecret);
} catch (err: any) {
console.error("Webhook signature verification failed:", err.message);
logger.error({ err }, "Webhook signature verification failed");
res.status(400).json({ error: "Invalid signature" });
return;
}
@ -133,11 +134,11 @@ router.post("/webhook", async (req: Request, res: Response) => {
return productId === DOCFAST_PRODUCT_ID;
});
if (!hasDocfastProduct) {
console.log(`Ignoring event for different product (session: ${session.id})`);
logger.info({ sessionId: session.id }, "Ignoring event for different product");
break;
}
} catch (err: any) {
console.error(`Failed to retrieve session line_items: ${err.message}, skipping`);
logger.error({ err, sessionId: session.id }, "Failed to retrieve session line_items");
break;
}
@ -147,14 +148,14 @@ router.post("/webhook", async (req: Request, res: Response) => {
}
const keyInfo = await createProKey(email, customerId);
console.log(`checkout.session.completed: provisioned pro key for ${email} (customer: ${customerId}, key: ${keyInfo.key.slice(0, 12)}...)`);
logger.info({ email, customerId }, "checkout.session.completed: provisioned pro key");
break;
}
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription;
const customerId = sub.customer as string;
await revokeByCustomer(customerId);
console.log(`Subscription cancelled for ${customerId}, key revoked`);
logger.info({ customerId }, "Subscription cancelled, key revoked");
break;
}
default: