[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,9 +1,49 @@
|
||||
const express = require('express');
|
||||
const { DOCKER } = require('../src/utilities/constants');
|
||||
const { paginate, parsePaginationParams } = require('../src/utilities/pagination');
|
||||
const { NotFoundError } = require('../src/utilities/errors');
|
||||
const { NotFoundError, ValidationError } = require('../src/utilities/errors');
|
||||
const { success } = require('../src/utils/responses');
|
||||
|
||||
/**
|
||||
* Validate a Docker container identifier (ID or name).
|
||||
* Allows hex container IDs and Docker-compliant names.
|
||||
* Blocks path traversal and shell metacharacters.
|
||||
* @param {string} id - Container ID or name from route param
|
||||
* @throws {ValidationError} if the ID is malformed
|
||||
*/
|
||||
function validateContainerId(id) {
|
||||
if (!id || typeof id !== 'string') {
|
||||
throw new ValidationError('Container ID is required');
|
||||
}
|
||||
// Docker names: [a-zA-Z0-9][a-zA-Z0-9_.-]*
|
||||
// Docker IDs: 64-char hex — also matches the above pattern
|
||||
// Max 128 chars covers IDs and names
|
||||
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(id)) {
|
||||
throw new ValidationError('Invalid container ID format');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate numeric resource limits for container update.
|
||||
* @param {*} memory - Memory in MB (optional)
|
||||
* @param {*} cpus - CPU count (optional)
|
||||
* @throws {ValidationError} if values are out of range
|
||||
*/
|
||||
function validateResourceLimits(memory, cpus) {
|
||||
if (memory !== undefined) {
|
||||
const memNum = Number(memory);
|
||||
if (isNaN(memNum) || memNum < 0 || memNum > 1048576) {
|
||||
throw new ValidationError('Memory must be a number between 0 and 1048576 MB');
|
||||
}
|
||||
}
|
||||
if (cpus !== undefined) {
|
||||
const cpuNum = Number(cpus);
|
||||
if (isNaN(cpuNum) || cpuNum < 0 || cpuNum > 1024) {
|
||||
throw new ValidationError('CPUs must be a number between 0 and 1024');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Containers route factory
|
||||
* @param {Object} deps - Explicit dependencies
|
||||
@@ -18,6 +58,7 @@ module.exports = function({ docker, log, asyncHandler, workflowEngine }) {
|
||||
|
||||
// Helper: verify container exists before operating on it
|
||||
async function getVerifiedContainer(id) {
|
||||
validateContainerId(id);
|
||||
const container = docker.client.getContainer(id);
|
||||
try {
|
||||
await container.inspect();
|
||||
@@ -205,6 +246,10 @@ module.exports = function({ docker, log, asyncHandler, workflowEngine }) {
|
||||
router.put('/:id/resources', asyncHandler(async (req, res) => {
|
||||
const container = await getVerifiedContainer(req.params.id);
|
||||
const { memory, cpus } = req.body;
|
||||
|
||||
// Validate resource limits before applying to Docker
|
||||
validateResourceLimits(memory, cpus);
|
||||
|
||||
const updateConfig = {};
|
||||
|
||||
if (memory !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user