fix(security): escape newlines in safe_substitute to prevent sed injection (#2718)

The safe_substitute() function in discovery.sh, qa.sh, refactor.sh, and
security.sh escaped \, &, and | but not newlines. A newline in the
replacement value would break the sed s command, causing failure or
unexpected behavior. Add newline escaping (backslash + literal newline)
after the existing metacharacter escaping.

Fixes #2702

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-03-17 12:28:39 -07:00 committed by GitHub
parent 0e5bfd830b
commit 2e26d56625
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 11 additions and 0 deletions

View file

@ -36,12 +36,15 @@ log_error() { printf "${RED}[discovery]${NC} %s\n" "$1"; echo "[$(date +'%Y-%m-%
# --- Safe sed substitution (escapes sed metacharacters in replacement) ---
# Usage: safe_substitute PLACEHOLDER VALUE FILE
# Escapes \, &, |, and newlines in VALUE to prevent sed injection.
safe_substitute() {
local placeholder="$1"
local value="$2"
local file="$3"
local escaped
escaped=$(printf '%s' "$value" | sed -e 's/[\\]/\\&/g' -e 's/[&]/\\&/g' -e 's/[|]/\\|/g')
# Escape literal newlines for sed replacement (backslash + newline)
escaped="${escaped//$'\n'/\\$'\n'}"
sed -i.bak "s|${placeholder}|${escaped}|g" "$file"
rm -f "${file}.bak"
}

View file

@ -77,6 +77,8 @@ safe_substitute() {
# Escape backslashes first, then &, then the delimiter |
local escaped
escaped=$(printf '%s' "$value" | sed -e 's/[\\]/\\&/g' -e 's/[&]/\\&/g' -e 's/[|]/\\|/g')
# Escape literal newlines for sed replacement (backslash + newline)
escaped="${escaped//$'\n'/\\$'\n'}"
sed -i.bak "s|${placeholder}|${escaped}|g" "$file"
rm -f "${file}.bak"
}

View file

@ -46,12 +46,15 @@ log() {
# --- Safe sed substitution (escapes sed metacharacters in replacement) ---
# Usage: safe_substitute PLACEHOLDER VALUE FILE
# Escapes \, &, |, and newlines in VALUE to prevent sed injection.
safe_substitute() {
local placeholder="$1"
local value="$2"
local file="$3"
local escaped
escaped=$(printf '%s' "$value" | sed -e 's/[\\]/\\&/g' -e 's/[&]/\\&/g' -e 's/[|]/\\|/g')
# Escape literal newlines for sed replacement (backslash + newline)
escaped="${escaped//$'\n'/\\$'\n'}"
sed -i.bak "s|${placeholder}|${escaped}|g" "$file"
rm -f "${file}.bak"
}

View file

@ -93,12 +93,15 @@ log() {
# --- Safe sed substitution (escapes sed metacharacters in replacement) ---
# Usage: safe_substitute PLACEHOLDER VALUE FILE
# Escapes \, &, |, and newlines in VALUE to prevent sed injection.
safe_substitute() {
local placeholder="$1"
local value="$2"
local file="$3"
local escaped
escaped=$(printf '%s' "$value" | sed -e 's/[\\]/\\&/g' -e 's/[&]/\\&/g' -e 's/[|]/\\|/g')
# Escape literal newlines for sed replacement (backslash + newline)
escaped="${escaped//$'\n'/\\$'\n'}"
sed -i.bak "s|${placeholder}|${escaped}|g" "$file"
rm -f "${file}.bak"
}