Found during the 2026-09-26 cloud sweep. #21 routed ConfigStore.persist, FileStore, ensure_seeded and the directory helpers through secure_open/secure_makedirs, but the store behind the headline flow was not covered. That store backs config_getter, simple_config_getter and get_configs_local_store: a dol TextFiles, one file per key. It still writes with a plain open, so value files get the umask default.
Reproduction (Linux, config2py 0.1.54)
A folder created by an older config2py, or by the user, is 0o755. _default_folder_setup only tightens folders it creates itself.
cd "$(mktemp -d)" && mkdir -p .config/config2py/configs && chmod 755 .config .config/config2py .config/config2py/configs
HOME=$PWD python -c "
import os
from config2py import config_getter
config_getter.configs['OPENAI_API_KEY'] = 'sk-demo'
p = os.path.expanduser('~/.config/config2py/configs')
print('dir', oct(os.stat(p).st_mode & 0o777), 'file', oct(os.stat(p + '/OPENAI_API_KEY').st_mode & 0o777))
"
# dir 0o755 file 0o644 <- the saved key is readable by every local user
A fresh install is protected by its 0o700 folder, but the file itself is still 0o644. The same holds for any folder path passed to simple_config_getter.
Why not fixed in the sweep
The sweep's code scope was #13 and #16. Also, get_configs_local_store returns a plain dol.TextFiles, and swapping in a subclass changes the public return type for about 30 dependents.
Proposed fix
- Add a
SecureTextFiles(TextFiles) in config2py/tools.py whose __setitem__ writes through secure_open. Return it from get_configs_local_store for the folder and app-name cases. Keep it an isinstance of TextFiles.
- In
get_configs_folder_for_app, re-tighten an existing config2py-managed folder (one that has the .config2py marker) with secure_makedirs.
- POSIX-only tests in
config2py/tests/test_secure_io.py: a pre-existing 0o755 folder plus config_getter.configs[k] = v gives a 0o600 file and a 0o700 folder.
- Run the dependents gate.
Found during the 2026-09-26 cloud sweep. #21 routed
ConfigStore.persist,FileStore,ensure_seededand the directory helpers throughsecure_open/secure_makedirs, but the store behind the headline flow was not covered. That store backsconfig_getter,simple_config_getterandget_configs_local_store: a dolTextFiles, one file per key. It still writes with a plainopen, so value files get the umask default.Reproduction (Linux, config2py 0.1.54)
A folder created by an older config2py, or by the user, is
0o755._default_folder_setuponly tightens folders it creates itself.A fresh install is protected by its
0o700folder, but the file itself is still0o644. The same holds for any folder path passed tosimple_config_getter.Why not fixed in the sweep
The sweep's code scope was #13 and #16. Also,
get_configs_local_storereturns a plaindol.TextFiles, and swapping in a subclass changes the public return type for about 30 dependents.Proposed fix
SecureTextFiles(TextFiles)inconfig2py/tools.pywhose__setitem__writes throughsecure_open. Return it fromget_configs_local_storefor the folder and app-name cases. Keep it anisinstanceofTextFiles.get_configs_folder_for_app, re-tighten an existing config2py-managed folder (one that has the.config2pymarker) withsecure_makedirs.config2py/tests/test_secure_io.py: a pre-existing0o755folder plusconfig_getter.configs[k] = vgives a0o600file and a0o700folder.