Repository navigation
fix(security): Credential and collected-data config files are written world-readable (0644) under ~/.colleague-skill/ - #214
Open
failsafesecurity wants to merge 1 commit into
Conversation
… world-readable (0644) under ~/.colleague-skill/
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.
Security fix: Credential and collected-data config files are written world-readable (0644) under ~/.colleague-skill/
Severity: HIGH (CVSS 8.2)
CWE: CWE-732
File:
tools/feishu_auto_collector.pyWhat this fixes
Every Feishu, DingTalk and Slack collector — plus the Feishu MCP client — persists its provider credentials as plaintext JSON under ~/.colleague-skill/ using
Path.write_text()with no mode argument, noos.open(..., 0o600), nochmodand no umask handling. Python therefore creates each file at 0666 masked by the process umask, which on a typical host with umask 022 yields mode 0644, and creates the containing directory at 0755. The result is that long-lived secrets such asapp_secret,user_access_token,refresh_tokenand the Slackxoxb-bot_tokenare readable by every other local user and by any process running under another account on the same machine.The writers are the four
save_configfunctions intools/feishu_auto_collector.py:72-74,tools/feishu_mcp_client.py:56-58,tools/dingtalk_auto_collector.py:58-60andtools/slack_auto_collector.py:102-104. Each builds the path fromPath.home()/.colleague-skill/<provider>_config.jsonwithmkdir(parents=True, exist_ok=True)followed bywrite_text(json.dumps(config)), and no code anywhere in the repository callsos.openwith a mode,chmod, or restricts the umask.The same default-permission pattern is used for the collected conversation and document output, so harvested private data is exposed alongside the secrets. This appears in the Feishu knowledge output writers (
tools/feishu_auto_collector.py:855-883),tools/slack_auto_collector.py:619and:642,tools/dingtalk_auto_collector.py:700,:711,:724,:742,tools/feishu_parser.py:243-244andtools/email_parser.py:331-332.The repository gives users false assurance that this is handled.
.gitignore:13-14lists~/.colleague-skill/under a heading about credential configs, but gitignore patterns are repository-root-relative and treat the tilde literally, so the rule never matches the real files, which live outside the working tree underPath.home(). MeanwhileCONTRIBUTING.md:93explicitly promises these files are written with permission 0600, which the code does not do.save_configattools/feishu_auto_collector.py:72-74performsCONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)followed byCONFIG_PATH.write_text(json.dumps(config, ...)), whereCONFIG_PATHisPath.home()/'.colleague-skill'/<provider>_config.json. The identical pattern appears attools/feishu_mcp_client.py:56-58,tools/dingtalk_auto_collector.py:58-60andtools/slack_auto_collector.py:102-104.mkdirwithout a mode yields 0755 andwrite_textwithoutos.open/mode yields 0644 under a default umask 022. A repository-wide grep forchmod|0o600|umask|mode=0o|os.openreturns zero matches.The concrete paths derive from
Path.home()attools/feishu_mcp_client.py:45,tools/feishu_auto_collector.py:59,tools/dingtalk_auto_collector.py:45andtools/slack_auto_collector.py:57, producing~/.colleague-skill/feishu_config.json(written bytools/feishu_auto_collector.py:73-74, whose config is built at lines 112-120, and bytools/feishu_mcp_client.py:57-58),~/.colleague-skill/dingtalk_config.json(tools/dingtalk_auto_collector.py:59-60) and~/.colleague-skill/slack_config.json(tools/slack_auto_collector.py:102-104). The values written includeapp_secret(tools/feishu_mcp_client.py:76,tools/feishu_auto_collector.py:112,tools/dingtalk_auto_collector.py:84),bot_token(tools/slack_auto_collector.py:150), anduser_access_token/refresh_token(tools/feishu_auto_collector.py:117,:913-914).Output files follow the same convention:
tools/feishu_auto_collector.py:855(msg_path.write_text(msg_content)),:866,:881-883;tools/slack_auto_collector.py:619and:642;tools/dingtalk_auto_collector.py:700,:711,:724,:742;tools/feishu_parser.py:243-244(open(args.output, "w")); andtools/email_parser.py:331-332. None of them pass a mode toos.openor callchmodafterwards, and some default into the repository tree rather than a protected directory.Observed file state on a default-umask host:
644 /home/user/.colleague-skill/feishu_config.jsoncontaining{"app_id":"cli_xxx","app_secret":"<real secret>","user_access_token":"u-<real token>"}, versus the expected600/Permission deniedfor other users..gitignore:13-14does not expand~; the~/.colleague-skill/line anchors to<repo>/~/.colleague-skill/, a path that never exists, so it matches nothing and gives false assurance. The real configs are absolutePath.home()/...paths and are not under ignore coverage.CONTRIBUTING.md:93states the files are written with permission 0600; the code does not do so.Preconditions: Exploitation requires another local user, co-tenant process, backup or sync agent on the same shared host — a multi-user workstation, shared build machine or CI runner — and the process umask must permit group or other read, which the common default 022 does. The same exposure results if the config directory is copied or symlinked into project scope, or if a backup or sync agent with access to
$HOMEreads it.The change
Added
import osand replaced allwrite_textcalls for credential config and collected-data output files withos.open(..., 0o600)+os.fdopen+os.chmod(0o600). Fixed allmkdircalls to usemode=0o700and addedos.chmod(dir, 0o700)to correct pre-existing world-readable directories. This covers save_config (credential file), collect_all (messages.txt, docs.txt, collection_summary.json), and main (messages.txt with --open-id path).Impact if unpatched
Any other local user or process can read the Feishu and DingTalk app secrets and the Slack bot token, then impersonate the configured app, bot or user from anywhere on the network — including reading private corporate chats, messages, documents and directory summaries. Where collector output is also world-readable, the captured private messages, emails and documents are exposed directly, without needing to use the credentials at all.
Prepared by OpenClaw BountyBot. Finding surfaced by Failsafe Nexus (Pandora) and the patch adversarially reviewed by GLM. Please review carefully — automated patches are a starting point, not a guarantee.