mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-29 03:36:55 +00:00
refactor(backend): sanitize user input before logging to close CRLF injection (#807)
* refactor(backend): sanitize user input before logging to close CRLF injection
Adds a small sanitizeForLog helper that strips CR, LF, tab, and ASCII
control characters (0x00-0x1F, 0x7F) from a value before it is embedded
in a console.log/warn/error/debug call. Wraps every call site where a
user-controlled value (req.params, req.body, req.query, or a value
derived from them) flows into a log message.
Closes the bulk of the open CodeQL alerts in this family:
- 96 js/log-injection
- 28 js/tainted-format-string
The helper is in backend/src/utils/safeLog.ts. Routes still pre-validate
input at the request boundary; this is the second line of defense and
gives static analyzers a sanitizer they can trace through. JSON
responses, Docker filter labels, and other non-log call sites are
intentionally left unwrapped.
* refactor(backend): printf-style format strings for tainted-log call sites
CodeQL's js/tainted-format-string rule flags template literals in the first
arg of console.X when any interpolated value is user-controlled, regardless
of whether each value is sanitized inline. The canonical mitigation is to
use a static format string and pass values as positional args.
Converts the 28 flagged template literals to printf-style ("%s") format
strings, with sanitizeForLog applied to each positional arg. Also fills in
the log-injection wraps on 9 sites where a user-controlled value was
missed in the first sweep (agents, fleet, gitSources, imageUpdates,
GitSourceService).
No behavior change at runtime. Node's util.format substitutes %s tokens
identically to template-literal interpolation.
* fix(backend): wrap nodeId/snapshotId in fleet restore debug log
CodeQL flagged the unwrapped numeric args even though they cannot
contain control chars in practice. Apply the sanitizer for taint-flow
recognition.
This commit is contained in:
@@ -3,6 +3,7 @@ import http from 'http';
|
||||
import { CryptoService } from './CryptoService';
|
||||
import { DatabaseService, type Registry, type RegistryType } from './DatabaseService';
|
||||
import { isDebugEnabled } from '../utils/debug';
|
||||
import { sanitizeForLog } from '../utils/safeLog';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -141,7 +142,7 @@ function httpGet(
|
||||
nextHeaders = rest;
|
||||
}
|
||||
if (isDebugEnabled()) {
|
||||
console.debug(`[RegistryService][debug] redirect ${status} ${url} -> ${nextUrl.toString()} (auth ${nextHeaders === headers ? 'kept' : 'stripped'})`);
|
||||
console.debug(`[RegistryService][debug] redirect ${status} ${sanitizeForLog(url)} -> ${sanitizeForLog(nextUrl.toString())} (auth ${nextHeaders === headers ? 'kept' : 'stripped'})`);
|
||||
}
|
||||
httpGet(nextUrl.toString(), nextHeaders, timeoutMs, false).then(resolve, reject);
|
||||
return;
|
||||
@@ -208,7 +209,7 @@ export class RegistryService {
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
console.info(`[RegistryService] Registry created: id=${id} type=${input.type} name="${input.name}"`);
|
||||
console.info(`[RegistryService] Registry created: id=${id} type=${sanitizeForLog(input.type)} name="${sanitizeForLog(input.name)}"`);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -282,7 +283,7 @@ export class RegistryService {
|
||||
return { success: false, error: 'AWS region is required for ECR registries.' };
|
||||
}
|
||||
if (isDebugEnabled()) {
|
||||
console.debug(`[RegistryService][debug] testWithCredentials ECR region=${input.aws_region}`);
|
||||
console.debug(`[RegistryService][debug] testWithCredentials ECR region=${sanitizeForLog(input.aws_region)}`);
|
||||
}
|
||||
await this.fetchEcrToken(input.username, input.secret, input.aws_region);
|
||||
if (isDebugEnabled()) {
|
||||
@@ -294,7 +295,7 @@ export class RegistryService {
|
||||
const probeUrl = toProbeUrl(input.url, input.type);
|
||||
const basicAuth = Buffer.from(`${input.username}:${input.secret}`).toString('base64');
|
||||
if (isDebugEnabled()) {
|
||||
console.debug(`[RegistryService][debug] testWithCredentials probing ${probeUrl}/v2/`);
|
||||
console.debug(`[RegistryService][debug] testWithCredentials probing ${sanitizeForLog(probeUrl)}/v2/`);
|
||||
}
|
||||
const res = await httpGet(`${probeUrl}/v2/`, { Authorization: `Basic ${basicAuth}` });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user