fix(speech): dictation language selection, agent socket permissions, log cap - #47
Open
killme2008 wants to merge 6 commits into
Open
fix(speech): dictation language selection, agent socket permissions, log cap#47killme2008 wants to merge 6 commits into
killme2008 wants to merge 6 commits into
Conversation
/tmp is world-writable, so any local user can bind ahakey.sock before the agent does. The agent steps aside when it finds an existing listener, so a squatter would then answer every approval request with switchState 0 and silently defeat the physical lever the approval flow is built on. Put the socket under ~/Library/Application Support/AhaKeyConfig (0700), create it 0600 with a tightened umask around bind(), and drop connections whose peer uid is not ours. Processes running as the same user can still connect; Unix permissions cannot express that boundary.
makeSpeechRecognizer() only ever tried Locale.preferredLanguages.first and otherwise fell back to the system default recognizer. On a Mac whose UI language is English but whose owner dictates Chinese, the preferred list reads ["en-CN", "zh-Hans-CN"] — only the first entry was consulted, so zh-Hans-CN was never reached and Chinese speech was transcribed by an English model. SFSpeechRecognizer compounds this: asking for en-CN hands back en-US without saying so, and nothing surfaced which model was actually running. - Add a persisted language preference, exposed as a picker on the voice key's input-method box and in the onboarding "try it" step, where a wrong language shows up on the very first recording. - Resolve candidates against supportedLocales() instead of trusting an arbitrary identifier, and show which language will actually be used. - Automatic resolution keeps preferred-language order and defers "which region for this language" to SFSpeechRecognizer, because supportedLocales() is a Set — en alone has 13 regional variants, so picking from it directly is not stable between runs. The onboarding view receives a value plus a bag of closures, so the preference reaches it through AhaKeyOnboardingPermissionState and a new setSpeechLocale action rather than by handing it the service.
The matching rules are the part of the language handling most likely to rot: exact language+region, fall back to language alone, skip tags that parse to nothing. They lived as private statics on a @mainactor class, unreachable from a test. Move them to SpeechLocaleResolver, inject the "which region for this language" step so tests do not depend on the host's installed speech assets, and add the package's first test target.
native-speech.log records every transcript verbatim — rawText and outputText, i.e. everything the user has ever dictated — and nothing ever truncated it. On a machine that has been dictating for a while it is the most sensitive file the app writes, with no bound on how far back it goes. Rotate at 1 MB and keep five files at most, dropping the oldest, so recent history stays available for debugging while the tail ages out.
The package gained its first test target in this branch; without a step here the tests would never run on CI.
sun_path holds 104 bytes and the fill sites used strcpy without a length check. /tmp/ahakey.sock was 16 bytes so the margin was never at risk; the new path under the home directory is 65 bytes for a short user name and grows with it, leaving room for roughly 44 characters before the copy overflows. Add AhaKeySocket.makeAddress, which refuses paths that do not fit and copies with strlcpy, and route all six fill sites through it. AgentManager and PluginHost carry their own copy since the targets do not share sources.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
1. 系统语言设为英文时,中文语音被英语模型转写
makeSpeechRecognizer()只取Locale.preferredLanguages.first。系统语言是英文时首选列表为["en-CN", "zh-Hans-CN"],中文轮不到。SFSpeechRecognizer还会把en-CN静默归一化成en-US。加一个持久化的语言设置,入口在语音键的「语音输入方式」和引导的「开始体验」,权限诊断弹窗显示当前生效语言。自动推断仍按首选语言顺序,改为在
supportedLocales()内匹配;只给语言码时由SFSpeechRecognizer决定地区,因为supportedLocales()是Set,en的 13 个变体取出来不稳定。2. Agent 控制 socket 可被冒充
/tmp全局可写,startSocketListener()发现已有监听会主动让位。其他本机用户可抢先 bind,对每个{"cmd":"permission"}回switchState: 0,全部自动批准。socket 移到
~/Library/Application Support/AhaKeyConfig/agent.sock(目录0700),bind()时用umask+chmod置为0600,accept()后getpeereid()校验 uid。同 uid 进程仍可连接,Unix 权限位管不了这层。路径引用同步更新
HookSupport、AhaKeyAgent、main.swift、PluginHost、AgentManager、scripts/ahakey-state.sh。sun_path只有 104 字节,原先各处用strcpy填充且无长度检查。/tmp/ahakey.sock是 16 字节所以一直没事,新路径在短用户名下就有 65 字节,用户名超过约 44 字符会溢出。新增AhaKeySocket.makeAddress,放不下就返回 nil,改用strlcpy,六处填充点全部走它。3. native-speech.log 无上限
逐字记录每次转写且从不截断。改为 1 MB 轮转,最多 5 个文件。
测试
新增 test target 和 CI 的
swift test。16 个用例覆盖语言匹配与日志轮转。macOS 15 / Swift 6.2 clean build + test 通过。安装后确认选中文可使
locale=zh-CN生效,socket 为srw-------,部署目标仍为 macOS 12。