Skip to content

[Bug] Entire ~/.iac-code/ directory and sensitive files created with world-readable permissions #38

@Prodesire

Description

@Prodesire

Bug Description

The entire ~/.iac-code/ configuration directory — including credential files, session history, logs, and tool results — is created with default file permissions (0644 for files, 0755 for directories), making them readable by all users on the system. These files may contain API keys, conversation history with sensitive information, and cloud credentials.

Affected Files and Directories

Path Current Expected Contains
~/.iac-code/ 0755 0700 All config
~/.iac-code/.credentials.yml 0644 0600 API keys
~/.iac-code/.cloud-credentials.yml 0644 0600 Cloud AK/SK
~/.iac-code/projects/**/*.jsonl 0644 0600 Session history
~/.iac-code/logs/*.log 0644 0600 Debug logs
~/.iac-code/tool-results/ 0755 0700 Tool execution output

Steps to Reproduce

  1. Run iac-code and configure credentials via /auth
  2. Check permissions:
    ls -la ~/.iac-code/.credentials.yml
    # -rw-r--r--  1 user  staff  ... .credentials.yml
    
    stat -c '%a' ~/.iac-code/
    # 755
    
    find ~/.iac-code/ -name "*.jsonl" -perm /044 | wc -l
    # All session files are world-readable

Expected Behavior

  • Config directory: 0700 (owner access only)
  • Credential files: 0600 (owner read/write only)
  • Session files: 0600 (may contain sensitive conversation content)
  • Log files: 0600 (may contain error messages with credentials)

Actual Behavior

All files and directories use default umask permissions (0644/0755), readable by any user on the system.

Root Cause

  1. Credential files: _save_yaml in src/iac_code/config.py uses path.write_text() without restricting permissions:

    def _save_yaml(path: Path, data: dict[str, Any]) -> None:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(yaml.dump(data, ...), encoding="utf-8")
  2. Config directory: get_config_dir() creates the directory with default permissions:

    config_dir.mkdir(parents=True, exist_ok=True)  # inherits umask (typically 022)
  3. Session and log files: Written via standard open() / json.dumps() without explicit permission setting.

Suggested Fix

The codebase already has restrict_file_permissions() in src/iac_code/utils/file_security.py (used by the A2A push subsystem), but it is NOT applied to the main config directory or credentials:

from iac_code.utils.file_security import restrict_file_permissions

# Fix 1: Restrict config directory on creation
def get_config_dir() -> Path:
    config_dir = _resolve_config_dir()
    created = not config_dir.exists()
    config_dir.mkdir(parents=True, exist_ok=True)
    if created:
        restrict_file_permissions(config_dir, directory=True)
    return config_dir

# Fix 2: Restrict credential files after write
def _save_yaml(path: Path, data: dict[str, Any], *, restricted: bool = False) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(yaml.dump(data, ...), encoding="utf-8")
    if restricted:
        restrict_file_permissions(path, directory=False)

Security Impact

  • Attack vector: Any local user on the same system can read API keys, cloud credentials, and session history
  • Affected data: API keys, AccessKey/SecretKey pairs, conversation content, debug logs
  • Severity: Medium on multi-user systems (servers, shared workstations), Low on personal machines
  • Note: Tools like ssh, gpg, and aws-cli refuse to use or create key files with overly permissive permissions for this reason

Operating System

macOS

Python Version

3.12.7

iac-code Version

0.2.3

Additional Context

  • The restrict_file_permissions() utility already exists and is used by a2a/push_queue.py and a2a/push_secrets.py, but not by the core config/credential/session writing paths.
  • The keyring package is listed as a dependency but only used by qwenpaw_source.py, not for main credential storage.
  • Session files (.jsonl) may contain tool outputs that reference internal systems, file contents, or cloud resource details discussed in conversations.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions