Skip to content

Commit 6abab32

Browse files
committed
[Change] Vendored lib sync: tlog_lib 2.0.5, alert_lib 1.0.6, elog_lib 1.0.5, pkg_lib 1.0.7
[Change] tlog_lib 2.0.4 → 2.0.5: symlink guards on cursor reads and stale-protection touches [Change] alert_lib 1.0.5 → 1.0.6: Slack JSON-escape, email header injection defense, HTTPS validation [Change] elog_lib 1.0.4 → 1.0.5: C0 control char escape, symlink write guards, trap restore [Change] pkg_lib 1.0.6 → 1.0.7: rc.local permission preservation, metachar escape, TOCTOU fix
1 parent 2abbb3d commit 6abab32

6 files changed

Lines changed: 203 additions & 71 deletions

File tree

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ v2.0.1 | Mar 25 2026:
8080

8181
-- Changes --
8282

83+
[Change] Vendored lib sync: tlog_lib 2.0.5, alert_lib 1.0.6, elog_lib 1.0.5,
84+
pkg_lib 1.0.7
8385
[Change] Alert templates: consolidate summary into headers; drop "TOTAL" prefix from
8486
labels (HITS/CLEANED/QUARANTINED); add quarantine metrics; aligned column spacing
8587
[Change] Hook scans write to rolling hook.hits.log instead of creating session files;

CHANGELOG.RELEASE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ v2.0.1 | Mar 25 2026:
8080

8181
-- Changes --
8282

83+
[Change] Vendored lib sync: tlog_lib 2.0.5, alert_lib 1.0.6, elog_lib 1.0.5,
84+
pkg_lib 1.0.7
8385
[Change] Alert templates: consolidate summary into headers; drop "TOTAL" prefix from
8486
labels (HITS/CLEANED/QUARANTINED); add quarantine metrics; aligned column spacing
8587
[Change] Hook scans write to rolling hook.hits.log instead of creating session files;

files/internals/alert_lib.sh

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
_ALERT_LIB_LOADED=1
2222

2323
# shellcheck disable=SC2034
24-
ALERT_LIB_VERSION="1.0.5"
24+
ALERT_LIB_VERSION="1.0.6"
2525

2626
# Channel registry — consuming projects populate via alert_channel_register()
2727
# Uses parallel indexed arrays instead of declare -A to avoid scope issues
@@ -321,6 +321,17 @@ _alert_build_mime() {
321321
_alert_email_local() {
322322
local recip="$1" subject="$2" text_file="$3" html_file="$4" format="${5:-text}"
323323
local from="${ALERT_SMTP_FROM:-root@$(hostname -f 2>/dev/null || hostname)}"
324+
# Defense-in-depth: strip CR/LF from all values used in email headers
325+
recip="${recip//$'\r'/}"
326+
recip="${recip//$'\n'/}"
327+
subject="${subject//$'\r'/}"
328+
subject="${subject//$'\n'/}"
329+
from="${from//$'\r'/}"
330+
from="${from//$'\n'/}"
331+
local reply_to="${ALERT_EMAIL_REPLY_TO:-}"
332+
reply_to="${reply_to//$'\r'/}"
333+
reply_to="${reply_to//$'\n'/}"
334+
324335
local sendmail_bin mail_bin
325336
sendmail_bin=$(command -v sendmail 2>/dev/null || true)
326337
mail_bin=$(command -v mail 2>/dev/null || true)
@@ -339,8 +350,8 @@ _alert_email_local() {
339350
echo "From: $from"
340351
echo "To: $recip"
341352
echo "Subject: $subject"
342-
if [ -n "${ALERT_EMAIL_REPLY_TO:-}" ]; then
343-
echo "Reply-To: $ALERT_EMAIL_REPLY_TO"
353+
if [ -n "$reply_to" ]; then
354+
echo "Reply-To: $reply_to"
344355
fi
345356
echo ""
346357
cat "$text_file"
@@ -359,8 +370,8 @@ _alert_email_local() {
359370
echo "From: $from"
360371
echo "To: $recip"
361372
echo "Subject: $subject"
362-
if [ -n "${ALERT_EMAIL_REPLY_TO:-}" ]; then
363-
echo "Reply-To: $ALERT_EMAIL_REPLY_TO"
373+
if [ -n "$reply_to" ]; then
374+
echo "Reply-To: $reply_to"
364375
fi
365376
echo "Content-Type: text/html; charset=UTF-8"
366377
echo "Content-Transfer-Encoding: base64"
@@ -388,8 +399,8 @@ _alert_email_local() {
388399
echo "From: $from"
389400
echo "To: $recip"
390401
echo "Subject: $subject"
391-
if [ -n "${ALERT_EMAIL_REPLY_TO:-}" ]; then
392-
echo "Reply-To: $ALERT_EMAIL_REPLY_TO"
402+
if [ -n "$reply_to" ]; then
403+
echo "Reply-To: $reply_to"
393404
fi
394405
_alert_build_mime "$text_body" "$html_body"
395406
} | "$sendmail_bin" -t -oi
@@ -491,21 +502,25 @@ _alert_deliver_email() {
491502
if [ -n "${ALERT_SMTP_RELAY:-}" ]; then
492503
# relay path: always build full multipart MIME message
493504
local from="${ALERT_SMTP_FROM:-root@$(hostname -f 2>/dev/null || hostname)}"
505+
# Defense-in-depth: strip CR/LF from all header values to prevent injection
506+
recip="${recip//$'\r'/}"
507+
recip="${recip//$'\n'/}"
508+
from="${from//$'\r'/}"
509+
from="${from//$'\n'/}"
510+
local reply_to="${ALERT_EMAIL_REPLY_TO:-}"
511+
reply_to="${reply_to//$'\r'/}"
512+
reply_to="${reply_to//$'\n'/}"
494513
local text_body html_body
495514
text_body=$(cat "$text_file")
496-
if [ -n "$html_file" ] && [ -f "$html_file" ]; then
497-
html_body=$(cat "$html_file")
498-
else
499-
html_body=""
500-
fi
515+
html_body=$(cat "$html_file")
501516
local msg_file
502517
msg_file=$(mktemp "${ALERT_TMPDIR}/alert_relay_msg.XXXXXX")
503518
{
504519
echo "From: $from"
505520
echo "To: $recip"
506521
echo "Subject: $subject"
507-
if [ -n "${ALERT_EMAIL_REPLY_TO:-}" ]; then
508-
echo "Reply-To: $ALERT_EMAIL_REPLY_TO"
522+
if [ -n "$reply_to" ]; then
523+
echo "Reply-To: $reply_to"
509524
fi
510525
echo "Date: $(date -R 2>/dev/null || date)"
511526
_alert_build_mime "$text_body" "$html_body"
@@ -581,9 +596,10 @@ _alert_slack_post_message() {
581596
# Inject "channel" field after opening brace
582597
# Uses awk instead of sed to avoid delimiter collision if channel
583598
# contains / or & (sed s/// treats both as special characters)
584-
local modified_payload
599+
local modified_payload escaped_channel
585600
modified_payload=$(mktemp "${ALERT_TMPDIR}/alert_slack_msg.XXXXXX")
586-
ch="$channel" awk 'NR==1 && /^\{/ { print "{\"channel\":\"" ENVIRON["ch"] "\"," substr($0,2); next } { print }' \
601+
escaped_channel=$(_alert_json_escape "$channel")
602+
ch="$escaped_channel" awk 'NR==1 && /^\{/ { print "{\"channel\":\"" ENVIRON["ch"] "\"," substr($0,2); next } { print }' \
587603
"$payload_file" > "$modified_payload"
588604
# Write token to config file — keeps it out of /proc/PID/cmdline
589605
local auth_cfg
@@ -638,7 +654,7 @@ _alert_slack_upload() {
638654
local url_response upload_url file_id
639655
url_response=$(_alert_curl_post "https://slack.com/api/files.getUploadURLExternal" \
640656
-K "$auth_cfg" \
641-
-d "filename=$filename" \
657+
--data-urlencode "filename=$filename" \
642658
-d "length=$fsize") || { command rm -f "$auth_cfg"; return 1; }
643659
case "$url_response" in
644660
*'"ok":true'*) ;;
@@ -653,6 +669,18 @@ _alert_slack_upload() {
653669
upload_url=$(printf '%s' "$url_response" | sed -n 's/.*"upload_url" *: *"\([^"]*\)".*/\1/p')
654670
file_id=$(printf '%s' "$url_response" | sed -n 's/.*"file_id" *: *"\([^"]*\)".*/\1/p')
655671

672+
# Validate upload URL is HTTPS (defense-in-depth: reject http, file, ftp, etc.)
673+
# Case-insensitive match: MITM attacker could use any casing (${var,,} prohibited on bash 4.1)
674+
case "$upload_url" in
675+
[hH][tT][tT][pP][sS]://*)
676+
;;
677+
*)
678+
echo "alert_lib: Slack upload URL rejected (not https): ${upload_url:-(empty)}" >&2
679+
command rm -f "$auth_cfg"
680+
return 1
681+
;;
682+
esac
683+
656684
# Step 2: upload file content to presigned URL (no auth header needed)
657685
_alert_curl_post "$upload_url" -F "file=@$file_path" > /dev/null || {
658686
echo "alert_lib: Slack file upload to presigned URL failed." >&2

0 commit comments

Comments
 (0)