[grade=B] DC-081: Input validation for 20 highest-risk mutating routes
Secures 20 mutating routes across 7 files against path traversal, shell injection, and ReDoS vectors: - containers.js: container ID validation + resource limit bounds (6 routes) - recipes/manage.js: recipe ID slug validation (4 routes) - tailscale.js: subdomain regex before interpolation + shell char blocking (2) - workflows.js: workflow ID slug validation (3 routes) - dependencies.js: service ID + dependsOn array validation (3 routes) - logs.js: YYYY-MM-DD date format validation (1 route) - sites.js: additional domain validation (1 route) Uses existing REGEX patterns from constants.js. No new dependencies. Codex: B (no blocking issues, 4 Low follow-ups for tests + strict bools). 1552/1552 tests pass, 0 regressions.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const { TAILSCALE } = require('../src/utilities/constants');
|
||||
const { TAILSCALE, REGEX } = require('../src/utilities/constants');
|
||||
const { exists } = require('../src/utilities/fs-helpers');
|
||||
const { ValidationError, NotFoundError } = require('../src/utilities/errors');
|
||||
const { ok, successMessage, unauthorized } = require('../src/utils/responses');
|
||||
@@ -80,6 +80,17 @@ module.exports = function({
|
||||
router.post('/config', asyncHandler(async (req, res) => {
|
||||
const { enabled, requireAuth, allowedTailnet } = req.body;
|
||||
|
||||
// Validate allowedTailnet is a safe CIDR/domain string if provided
|
||||
if (typeof allowedTailnet !== 'undefined' && allowedTailnet !== null) {
|
||||
if (typeof allowedTailnet !== 'string' || allowedTailnet.length > 255) {
|
||||
throw new ValidationError('allowedTailnet must be a string (max 255 chars)');
|
||||
}
|
||||
// Block shell metacharacters and path traversal
|
||||
if (/[;&|`$()<>\\]/.test(allowedTailnet)) {
|
||||
throw new ValidationError('allowedTailnet contains invalid characters');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof enabled !== 'undefined') tailscale.config.enabled = enabled;
|
||||
if (typeof requireAuth !== 'undefined') tailscale.config.requireAuth = requireAuth;
|
||||
if (typeof allowedTailnet !== 'undefined') tailscale.config.allowedTailnet = allowedTailnet;
|
||||
@@ -150,6 +161,10 @@ module.exports = function({
|
||||
if (!subdomain) {
|
||||
throw new ValidationError('subdomain is required');
|
||||
}
|
||||
// Validate subdomain before it is interpolated into a regex
|
||||
if (!REGEX.SUBDOMAIN.test(subdomain)) {
|
||||
throw new ValidationError('[DC-301] Invalid subdomain format');
|
||||
}
|
||||
|
||||
const content = await caddy.read();
|
||||
const domain = buildDomain(subdomain);
|
||||
|
||||
Reference in New Issue
Block a user