Add input validation to prevent command injection

Security improvements:
- Language selection now uses dropdown with 31 supported languages
- Model path validated: must be .bin file, no path traversal
- Validation runs before transcription execution
- Invalid inputs show error status instead of executing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hariel1985
2026-02-02 12:40:54 +01:00
szülő e155c8d074
commit 66aed5edbd

Fájl megtekintése

@@ -11,6 +11,51 @@ struct Defaults {
static let playSounds = "playSounds"
}
// MARK: - Supported Languages (Whisper)
struct SupportedLanguages {
static let codes: [String: String] = [
"hu": "Magyar",
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português",
"nl": "Nederlands",
"pl": "Polski",
"ru": "Русский",
"uk": "Українська",
"cs": "Čeština",
"sk": "Slovenčina",
"ro": "Română",
"hr": "Hrvatski",
"sr": "Srpski",
"sl": "Slovenščina",
"ja": "日本語",
"zh": "中文",
"ko": "한국어",
"ar": "العربية",
"tr": "Türkçe",
"vi": "Tiếng Việt",
"th": "ไทย",
"el": "Ελληνικά",
"he": "עברית",
"hi": "हिन्दी",
"sv": "Svenska",
"da": "Dansk",
"fi": "Suomi",
"no": "Norsk"
]
static func isValid(_ code: String) -> Bool {
return codes.keys.contains(code.lowercased())
}
static var sortedCodes: [(code: String, name: String)] {
return codes.sorted { $0.value < $1.value }.map { (code: $0.key, name: $0.value) }
}
}
// MARK: - App Delegate
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem!
@@ -98,18 +143,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
langLabel.frame = NSRect(x: 20, y: y, width: labelWidth, height: 24)
contentView.addSubview(langLabel)
let langField = NSTextField(string: language)
langField.frame = NSRect(x: controlX, y: y, width: 60, height: 24)
langField.tag = 1
langField.target = self
langField.action = #selector(languageChanged(_:))
contentView.addSubview(langField)
let langHint = NSTextField(labelWithString: "(hu, en, de, fr, es...)")
langHint.frame = NSRect(x: 210, y: y, width: 150, height: 24)
langHint.textColor = .secondaryLabelColor
langHint.font = NSFont.systemFont(ofSize: 11)
contentView.addSubview(langHint)
let langPopup = NSPopUpButton(frame: NSRect(x: controlX, y: y, width: 180, height: 24), pullsDown: false)
langPopup.tag = 1
for lang in SupportedLanguages.sortedCodes {
langPopup.addItem(withTitle: "\(lang.name) (\(lang.code))")
langPopup.lastItem?.representedObject = lang.code
}
// Select current language
if let index = SupportedLanguages.sortedCodes.firstIndex(where: { $0.code == language }) {
langPopup.selectItem(at: index)
}
langPopup.target = self
langPopup.action = #selector(languageChanged(_:))
contentView.addSubview(langPopup)
y -= 40
@@ -167,13 +213,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return window
}
@objc func languageChanged(_ sender: NSTextField) {
language = sender.stringValue
@objc func languageChanged(_ sender: NSPopUpButton) {
if let code = sender.selectedItem?.representedObject as? String {
language = code
NSLog("Language changed to: \(language)")
}
}
@objc func modelPathChanged(_ sender: NSTextField) {
modelPath = sender.stringValue
let newPath = sender.stringValue
let validation = isValidModelPath(newPath)
if validation.valid {
modelPath = newPath
}
checkModelExists()
}
@@ -226,10 +278,31 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
// MARK: - Model Check
// MARK: - Model Validation
func isValidModelPath(_ path: String) -> (valid: Bool, error: String?) {
// Check extension
if !path.lowercased().hasSuffix(".bin") {
return (false, "Model must be a .bin file")
}
// Check for path traversal attempts
let normalized = (path as NSString).standardizingPath
if normalized.contains("..") {
return (false, "Invalid path")
}
// Check file exists
if !FileManager.default.fileExists(atPath: normalized) {
return (false, "Model not found")
}
return (true, nil)
}
func checkModelExists() {
if !FileManager.default.fileExists(atPath: modelPath) {
updateStatus("⚠️ Model not found")
let validation = isValidModelPath(modelPath)
if !validation.valid {
updateStatus("⚠️ \(validation.error ?? "Invalid model")")
} else {
updateStatus("Ready")
}
@@ -359,9 +432,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: - Transcription
func transcribe() {
// Validate inputs before execution
let modelValidation = isValidModelPath(modelPath)
guard modelValidation.valid else {
DispatchQueue.main.async {
self.statusItem.button?.title = "🎤"
self.updateStatus("⚠️ \(modelValidation.error ?? "Invalid model")")
if self.playSounds { NSSound(named: "Basso")?.play() }
}
return
}
guard SupportedLanguages.isValid(language) else {
DispatchQueue.main.async {
self.statusItem.button?.title = "🎤"
self.updateStatus("⚠️ Invalid language")
if self.playSounds { NSSound(named: "Basso")?.play() }
}
return
}
let task = Process()
task.executableURL = URL(fileURLWithPath: "/opt/homebrew/bin/whisper-cli")
task.arguments = ["-m", modelPath, "-l", language, "-f", audioFilePath]
task.arguments = ["-m", modelPath, "-l", language.lowercased(), "-f", audioFilePath]
let pipe = Pipe()
task.standardOutput = pipe