P1 fixes:
- P1-1 _handle_reload: empty string/0/false/falsy name no longer clears all
caches; only explicit null/omitted name triggers full cache clear; non-string
name raises ConfigError instead of being silently str()-converted
- P1-2 ConfigStore.get(): return dict(entry) shallow copy instead of direct
cache reference to prevent callers from mutating the internal cache
- P1-3 list(): resolve symlinks via os.path.realpath() and skip entries whose
real path escapes configs/ root; prevents leaking external file metadata
(size, mtime) through directory-traversing symlinks
- P1-4 _handle_get key: validate key is a string (isinstance check); reject
non-string/empty key with clear ConfigError instead of misleading str()
conversion leading to "key not found" errors
P2 fixes:
- P2-1 ConfigParser: use interpolation=None to disable %(key)s interpolation,
preventing InterpolationError on config values containing bare % characters
- P2-2 argparse: update description from "pymsg/TCP" to "pymsg RpcServer/TCP"
to match module docstring
Also update SECURITY_AUDIT_20260801.md to mark issues 9-14 as fixed and
refresh the security mechanisms summary table; all P0/P1 issues are now resolved.
- Split _resolve() into _validate_name() (pure string checks, lock-free
fast-fail) and _resolve_locked() (filesystem checks, must be called
under lock)
- Perform all filesystem operations (realpath → isfile → stat → open)
inside self._lock in get(), eliminating the window between path
validation and file open where a symlink swap could escape the
configs directory
- Apply same fix to reload(): validate name outside lock, resolve path
inside lock to prevent TOCTOU during targeted reload
- Hold lock during list() directory traversal so the `cached` flag
(which reads self._cache) is consistent with directory state; wrap
os.stat() in try/except to tolerate files disappearing mid-traversal
- Update docstring and README:
- Protocol description corrected to pymsg RpcServer/TCP
- Python client examples updated from legacy MsgClient to RpcClient
- Security section now documents TOCTOU mitigation and Slowloris
protection (added by earlier pymsg fix)
- Wrap _flush_loop main body in outer try/except to prevent any
unexpected exception (network errors, serialization errors, etc.)
from killing the background thread (which would cause unbounded
queue growth and eventual OOM)
- Wrap _send() call in inner try/except as final safety net
- Wrap RpcClient() construction in try/except (connection failures
during client creation were previously unhandled)
- Add _safe_json() helper: unserializable extra fields fall back to
repr()/str() instead of raising TypeError that could kill thread
- Generate timestamp at push() (enqueue time) instead of _send() time,
so timestamps reflect actual event time even when queue is backed up
- Replace unreliable time.sleep(0.6) in flush() with _flush_done Event
for proper synchronization between caller and background thread
- Add _QUEUE_MAXSIZE=10000 with drop-oldest policy to prevent unbounded
memory growth even in pathological cases
- Use put_nowait with Full handling to ensure push() never blocks
business threads
- Sync fix to all 4 copies: confs/static/smtp/dnfs
- Add MAX_CONFIG_SIZE constant (1 MiB) to limit single config file size
- Check file size via os.stat() before reading into memory
- Oversized files raise ConfigError with descriptive message instead of
being fully read (which could cause OOM with multi-GB files)
- Use single os.stat() call to get both mtime and size atomically
- Update README: document size limit in security mechanisms and notes
- Existing small configs (ini/json/yaml, typically <10KB) are unaffected