security: restrict network-backup endpoints to LAN IPs only
Requests from outside 10.x, 192.168.x, 172.16-31.x, 127.x are rejected with 403 before the API key is even checked. This prevents the database dump endpoint from being reachable from the internet even if the key leaked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,27 @@ const router = Router();
|
|||||||
|
|
||||||
const UPLOADS_DIR = path.resolve(process.cwd(), "uploads");
|
const UPLOADS_DIR = path.resolve(process.cwd(), "uploads");
|
||||||
|
|
||||||
function checkApiKey(req: Request, res: Response): boolean {
|
function isLanIp(ip: string): boolean {
|
||||||
|
return (
|
||||||
|
/^127\./.test(ip) ||
|
||||||
|
/^10\./.test(ip) ||
|
||||||
|
/^192\.168\./.test(ip) ||
|
||||||
|
/^172\.(1[6-9]|2\d|3[01])\./.test(ip)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAccess(req: Request, res: Response): boolean {
|
||||||
|
// Only allow requests from the local network
|
||||||
|
const ip =
|
||||||
|
(req.headers["x-real-ip"] as string) ||
|
||||||
|
(req.headers["x-forwarded-for"] as string)?.split(",")[0].trim() ||
|
||||||
|
req.socket.remoteAddress ||
|
||||||
|
"";
|
||||||
|
if (!isLanIp(ip)) {
|
||||||
|
res.status(403).json({ error: "Access restricted to local network" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const providedKey = req.headers["x-network-backup-key"] as string | undefined;
|
const providedKey = req.headers["x-network-backup-key"] as string | undefined;
|
||||||
if (!providedKey) {
|
if (!providedKey) {
|
||||||
res.status(401).json({ error: "Missing X-Network-Backup-Key header" });
|
res.status(401).json({ error: "Missing X-Network-Backup-Key header" });
|
||||||
@@ -25,7 +45,7 @@ function checkApiKey(req: Request, res: Response): boolean {
|
|||||||
|
|
||||||
// GET /api/database-management/network-backup
|
// GET /api/database-management/network-backup
|
||||||
router.get("/network-backup", async (req: Request, res: Response): Promise<any> => {
|
router.get("/network-backup", async (req: Request, res: Response): Promise<any> => {
|
||||||
if (!checkApiKey(req, res)) return;
|
if (!checkAccess(req, res)) return;
|
||||||
|
|
||||||
const pg = spawn(
|
const pg = spawn(
|
||||||
"pg_dump",
|
"pg_dump",
|
||||||
@@ -59,7 +79,7 @@ router.get("/network-backup", async (req: Request, res: Response): Promise<any>
|
|||||||
|
|
||||||
// GET /api/database-management/network-backup-files
|
// GET /api/database-management/network-backup-files
|
||||||
router.get("/network-backup-files", (req: Request, res: Response): any => {
|
router.get("/network-backup-files", (req: Request, res: Response): any => {
|
||||||
if (!checkApiKey(req, res)) return;
|
if (!checkAccess(req, res)) return;
|
||||||
|
|
||||||
if (!fs.existsSync(UPLOADS_DIR)) {
|
if (!fs.existsSync(UPLOADS_DIR)) {
|
||||||
return res.status(200).end();
|
return res.status(200).end();
|
||||||
|
|||||||
Reference in New Issue
Block a user