Version 1.0 ·
Date 2026-07-02 ·
CAP-Warn full codebase static review ·
39 total findings
Executive Summary
CAP-Warn is a PHP-based weather alert system for AllStar/repeater nodes. It fetches
NWS CAP alerts, optional NHC cyclone data, Raspberry Pi temperature monitoring, TTS,
and Pushover notifications. The most serious issues are shell command injection
surfaces, insecure storage/logging of API credentials, assignment-instead-of-comparison
bugs that break Pi alert behavior, and broken severe-weather audio logic due to case
mismatch.
Summary Counts
6
Critical Security
5
High Security
4
Medium Security
3
Low Security
4
Critical Bugs
8
High Bugs
7
Medium Bugs
5
Low Bugs
Quick Reference Table
ID
Severity
Category
File
Lines
Finding
BUG-001
Critical
Bug
usr/share/cap-warn/cap_temp.php
148, 192
Assignment (=) instead of comparison (==) — breaks Pi pushover modes
BUG-002
Critical
Bug
usr/share/cap-warn/cap_functions.php
943, 970
Call to undefined function add_word()
BUG-003
Critical
Bug
usr/share/cap-warn/cap_warn.php
384, 389-415
Severe weather "seek shelter" audio never plays (case bug)
BUG-004
Critical
Bug
usr/share/cap-warn/cap_temp.php
177
Undefined variable $high — emergency temperature tier broken
SEC-001
Critical
Security
usr/share/cap-warn/cap_functions.php
566, 574-575, 52, 977, 704
Command injection via unescaped shell arguments
SEC-002
Critical
Security
usr/share/cap-warn/setup.sh
575-602
API keys and tokens stored without restrictive permissions
SEC-003
Critical
Security
usr/share/cap-warn/setup.sh
223, 560-561
Secrets written to install logs in plaintext
SEC-004
Critical
Security
usr/bin/cap-warn/cap_warn.sh
12-18
World-writable runtime state files
SEC-005
Critical
Security
usr/bin/cap-warn/net-flag.sh
6, 10
Hub mute flag controllable by any local user
SEC-006
Critical
Security
usr/share/cap-warn/cap_functions.php
942-943
Overly permissive TTS temp directory
BUG-005
High
Bug
usr/share/cap-warn/cap_functions.php
819-977
Missing global $node in check_sound()
BUG-006
High
Bug
usr/share/cap-warn/cap_cyclones.php
49-57
Cyclone module: quiet hours broken when run standalone
BUG-007
High
Bug
usr/share/cap-warn/setup.sh
523
Setup script typo: $PIALARMS vs $piAlarms
BUG-008
High
Bug
etc/cap-warn/advanced_config.php
124-125
Double PHP closing tag in advanced config
BUG-009
High
Bug
usr/share/cap-warn/cap_temp.php
59
Wrong vcgencmd invocation
BUG-010
High
Bug
usr/share/cap-warn/cap_cyclones.php
190-191
Cyclone playback picks wrong file when VoiceRSS fails
BUG-011
High
Bug
usr/share/cap-warn/cap_functions.php
424-426
merge_alerts() crashes on invalid JSON
BUG-012
High
Bug
usr/share/cap-warn/cap_functions.php
163
isNewCycloneAdvisory() marks storm announced before playback
SEC-007
High
Security
usr/share/cap-warn/voicerss_tts.php
36
VoiceRSS API defaults to HTTP
SEC-008
High
Security
usr/share/cap-warn/cap_functions.php
326-337, 377-388, 438-449, 493-500
No TLS hardening on external HTTP clients
SEC-009
High
Security
usr/share/cap-warn/cap_functions.php
574-575
Privilege escalation via sudo Asterisk
SEC-010
High
Security
usr/share/cap-warn/cap_functions.php
71-78, 919
Unbounded log growth / sensitive data in archives
SEC-011
High
Security
usr/share/cap-warn/cap_pushover.php
36-67
Pushover messages lack input sanitization
BUG-013
Medium
Bug
usr/share/cap-warn/cap_warn.php
144-151
Misleading hold-flag message; playback not fully skipped
BUG-014
Medium
Bug
usr/share/cap-warn/cap_temp.php
128-141
Inverted/confusing low-voltage counter logic
BUG-015
Medium
Bug
usr/share/cap-warn/cap_temp.php
53, 157, 176, 183
Empty Pushover notifications for temperature
BUG-016
Medium
Bug
usr/share/cap-warn/cap_functions.php
463
fetchCapAlerts() deletes cache on network failure
BUG-017
Medium
Bug
usr/share/cap-warn/cap_functions.php
366
fetchZoneAlerts() may reference undefined $elapsed
BUG-018
Medium
Bug
usr/share/cap-warn/cap_functions.php
614
serial() deletes instance.id on every run
BUG-019
Medium
Bug
usr/share/cap-warn/setup.sh
635
Setup references undefined $BACKUP
BUG-020
Medium
Bug
etc/cap-warn/config.php
9-33
Placeholder config forces die until setup runs
BUG-021
Medium
Bug
usr/share/cap-warn/cap_warn.php
169-179
Clash-file mutual exclusion is weak
BUG-022
Medium
Bug
usr/share/cap-warn/cap_warn.php
96-97
GPS coordinate validation strips but does not validate range
BUG-023
Medium
Bug
etc/cap-warn/advanced_config.php
40
$reportAll config option unused
LOW-001
Low
Bug
usr/share/cap-warn/cap_warn.php
252
Deprecated <> operator
LOW-002
Low
Security
usr/share/cap-warn/cap_functions.php
589
cvlc path quoting fragile
LOW-003
Low
Bug
usr/share/cap-warn/cap_cyclones.php
98
Misleading cyclone log message
LOW-004
Low
Bug
usr/share/cap-warn/cap_temp.php
110
Throttle sound lookup likely fails
LOW-005
Low
Bug
usr/share/cap-warn/cap_functions.php
731
GPS HDOP rating typo
Critical Security Issues
[SEC-001] Command injection via unescaped shell arguments
Multiple exec()/shell_exec() calls interpolate variables without escapeshellarg(). $action is built from config, API text, and filesystem paths and passed to sox. $node is passed unescaped to asl-tts and sudo asterisk. $device from glob() is unescaped in cat command.
Related: usr/share/cap-warn/cap_warn.php:498
Impact: Local or config-level attackers may execute arbitrary commands as the script user (often root via cron/sudo).
[SEC-002] API keys and tokens stored without restrictive permissions
usr/share/cap-warn/setup.sh : 575-602
setup.sh writes /etc/cap-warn/config.php with VoiceRSS key, Pushover user key, and API token. No chmod 600 or ownership hardening applied.
Related: usr/share/cap-warn/setup_text.sh:251-275
Impact: Any local user may read Pushover/VoiceRSS credentials from disk (default umask often yields world-readable config).
[SEC-003] Secrets written to install logs in plaintext
usr/share/cap-warn/setup.sh : 223, 560-561
Full VoiceRSS API key logged to /var/log/cap-warn/install.log. Summary screen includes Pushover key and token.
Impact: Credential exposure to anyone who can read install logs.
[SEC-004] World-writable runtime state files
usr/bin/cap-warn/cap_warn.sh : 12-18
Sets /tmp/alert*, /tmp/warn*, /tmp/cyclone* files to chmod 666.
Related: usr/bin/cap_warn:12-18
Impact: Any local user can modify alert cache, hold flags, cyclone state, or playback flags — suppress alerts, force replays, or corrupt behavior.
[SEC-005] Hub mute flag controllable by any local user
usr/bin/cap-warn/net-flag.sh : 6, 10
Creates /tmp/linked_true.txt with no privilege check. cap_warn.php aborts all playback when this file exists.
Weather/cyclone/Pi status text passed directly to Pushover without length or content filtering.
Impact: Low direct risk; NWS alert text forwarded as-is to third-party service.
Critical Bugs
[BUG-001] Assignment (=) instead of comparison (==) — breaks Pi pushover modes
usr/share/cap-warn/cap_temp.php : 148, 192
if($pushNotify = "pi_pushover") assigns and is always true. Clears $action and suppresses voice regardless of configured mode.
Impact: pi_pushover, pi_both, and all modes behave incorrectly; temperature voice alerts likely never play when those lines execute.
[BUG-002] Call to undefined function add_word()
usr/share/cap-warn/cap_functions.php : 943, 970
Calls add_word($in) on TTS failure paths, but add_word() is fully commented out (lines 1060-1085).
Impact: PHP fatal error when TTS directory creation or sound file write fails — can stop entire cron job.
[BUG-003] Severe weather "seek shelter" audio never plays (case bug)
usr/share/cap-warn/cap_warn.php : 384, 389-415
Event names lowercased with strtolower(), then compared to mixed-case strings like "Tornado Warning". Comparisons never match.
Impact: Extra shelter/tornado/thunderstorm/hurricane/blizzard/burn-ban audio skipped on initial alert. Tail-file loop (453-487) does not lowercase — inconsistent behavior.
[BUG-004] Undefined variable $high — emergency temperature tier broken
usr/share/cap-warn/cap_temp.php : 177
if ($temp >= $high) references $high which is never defined. Only commented in advanced_config.php line 38.
Impact: PHP notices; emergency/warning/attention sounds for extreme heat never trigger.
High Bugs
[BUG-005] Missing global $node in check_sound()
usr/share/cap-warn/cap_functions.php : 819-977
check_sound() uses $node in asl-tts command (line 977) without declaring global $node.
Impact: Offline TTS fallback for missing sound files fails or uses empty node ID.
[BUG-006] Cyclone module: quiet hours broken when run standalone
usr/share/cap-warn/cap_cyclones.php : 49-57
Uses $hour and $day for sleep mute, but neither is set in cap_cyclones.php.
Impact: PHP warnings; quiet hours do not apply to cyclone announcements when module is invoked directly.
[BUG-007] Setup script typo: $PIALARMS vs $piAlarms
usr/share/cap-warn/setup.sh : 523
if [ "$PIALARMS" != "true" ] — $PIALARMS is never set; should be $piAlarms.
Impact: Pushover notification mode menu logic is wrong when Pi alarms are configured.
[BUG-008] Double PHP closing tag in advanced config
etc/cap-warn/advanced_config.php : 124-125
Two ?> closers. Trailing ?> can emit whitespace and cause "headers already sent" in web context.
Impact: Potential output corruption if file used via web server.
[BUG-009] Wrong vcgencmd invocation
usr/share/cap-warn/cap_temp.php : 59
exec("sudo /usr/bin/vcgencmd measure_temp 2", ...) — literal 2 passed as argument, not stderr redirection.
Impact: vcgencmd may fail; code falls back to sysfs.
[BUG-010] Cyclone playback picks wrong file when VoiceRSS fails
usr/share/cap-warn/cap_cyclones.php : 190-191
if ($tts) uses cycloneWAV else cycloneUL — ignores build_tts_audio() return value which may be .ul via asl-tts fallback.
Impact: Cyclone alert audio may not play when VoiceRSS is down but offline TTS succeeds.
[BUG-011] merge_alerts() crashes on invalid JSON
usr/share/cap-warn/cap_functions.php : 424-426
If cached file exists but json_decode() returns null, $pointData["features"] causes fatal error.
Impact: Corrupt or partial cache file can crash the alert pipeline.
[BUG-012] isNewCycloneAdvisory() marks storm announced before playback
usr/share/cap-warn/cap_functions.php : 163
Appends storm key to cyclone_key.log before audio is built or played.
Impact: TTS or playback failure permanently suppresses re-announcement of that advisory.
Medium Bugs
[BUG-013] Misleading hold-flag message; playback not fully skipped
usr/share/cap-warn/cap_warn.php : 144-151
Logs "Skipping playback — still within hold window" but does not return or set skip flag. New alerts still play.
Impact: Misleading logs; hold window only partially enforced.
$LoV_limit comment says "max alarms per day" but logic suppresses while count <= limit. Opposite pattern from $uv_limit. $LoV__counter defined but never used.
Impact: Confusing behavior; requires 12+ events before alerting when limit=10.
[BUG-015] Empty Pushover notifications for temperature