T09 · Insecure Skill Coding Practices
- Location
scripts/announce.py:103- Finding
AppleScript Injection Through Unvalidated Airfoil Volume Configuration
- Content
View full analysis
Vulnerability Details
File Location:
scripts/announce.py, lines 103–119 and 286–287
Vulnerability Type: AppleScript injection caused by unvalidated configuration data
Risk Level: HighVulnerable Code
python def setup_airfoil(speakers: list[str], volume: float = 0.7) -> bool: """Set Airfoil source to System-Wide Audio, connect speakers, and set volume.""" speaker_list = '", "'.join(_sanitize_applescript_string(s) for s in speakers) script = f''' tell application "Airfoil" set current audio source to system source "System-Wide Audio" set targetSpeakers to {{"{speaker_list}"}} repeat with s in (every speaker) if (name of s) is in targetSpeakers then connect to s set (volume of s) to {volume} else disconnect from s end if end repeat return "ok" end tell ''' result = subprocess.run( ["osascript", "-e", script], capture_output=True, text=True, timeout=30 ) return result.returncode == 0The value reaches this function directly from the JSON configuration:
python volume = config["airfoil"].get("volume", 0.7) if not setup_airfoil(speakers, volume): print("Airfoil setup failed", file=sys.stderr) sys.exit(1)Technical Analysis
The
airfoil.volumevalue is loaded fromconfig.jsonwithout checking that it is a finite numeric value in the documented range of0.0through1.0. Python type annotations do not enforce runtime types, so JSON can supply a string instead of a number.The value is then interpolated directly into executable AppleScript source:
python set (volume of s) to {volume}Speaker names are passed through
_sanitize_applescript_string, but no equivalent validation is applied tovolume. An attacker who can create o ...[truncated 2110 chars]- Remediation
View remediation
Remediation Suggestions
-
Validate the value immediately after loading the configuration. Accept only JSON numbers, explicitly reject strings and booleans, and enforce the documented range:
python import math raw_volume = config.get("airfoil", {}).get("volume", 0.7) if isinstance(raw_volume, bool) or not isinstance(raw_volume, (int, float)): raise ValueError("airfoil.volume must be a number") volume = float(raw_volume) if not math.isfinite(volume) or not 0.0 <= volume <= 1.0: raise ValueError("airfoil.volume must be finite and between 0.0 and 1.0") -
Keep validation at the execution boundary as defense in depth.
setup_airfoil()should validatevolumeagain rather than relying exclusively on its caller. -
Serialize the validated value using a fixed numeric representation before inserting it into AppleScript:
python safe_volume = format(volume, ".6f")Only
safe_volume, derived from an already validated numeric value, should be added to the script. -
Add configuration-schema validation for all externally controlled fields, including speaker arrays, voice identifiers, audio formats, chime paths, timeouts, and volume.
-
Treat
ANNOUNCER_WORKSPACEas a trust boundary. If the environment may be attacker-influenced, restrict the workspace to an approved absolute directory and verify configuration-file ownership and permissions before loading it. -
Add regression tests proving that string, boolean, infinite, non-finite, negative, and greater-than-one volume values are rejected before
osascriptis invoked.
-
