Skip to content

fix: enforce owner-only permissions on credential files#6242

Open
theCyberTech wants to merge 3 commits into
mainfrom
fix/credential-file-permissions
Open

fix: enforce owner-only permissions on credential files#6242
theCyberTech wants to merge 3 commits into
mainfrom
fix/credential-file-permissions

Conversation

@theCyberTech

@theCyberTech theCyberTech commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Two credential-at-rest files were left world-readable on multi-user hosts. Both stem from mkdir/open using default (umask-dependent) permissions instead of restricting to the owner. The token_manager.py module already writes its secrets atomically at 0o600; this brings the rest of the credential surface in line.

Findings

  1. TokenManager._get_secure_storage_path() — docstring promises a 0o700 credential directory, but mkdir(parents=True, exist_ok=True) created it at the default 0o755. The dir holds the Fernet secret.key and encrypted tokens.enc, so other local users could traverse/list it.
  2. Settings.dump() — persists tool_repository_password (plaintext) to ~/.config/crewai/settings.json via open("w"), producing a world-readable 0o644 file; the config dir was also 0o755.

Fixes

  • TokenManager: chmod(0o700) the credential dir after mkdir (robust against umask and pre-existing dirs).
  • Settings: write settings.json atomically (mkstempjson.dumpchmod 0o600os.replace) and chmod(0o700) the dedicated config dir. The /tmp and cwd fallback parents are deliberately not chmod'd — the 0o600 file mode protects the credential there without tightening a shared directory.

Threat model

Local information disclosure. Low impact on a single-user dev laptop; real exposure on shared/multi-user hosts and via the /tmp/cwd config fallbacks. Clearly a framework defect rather than operator error — the user never introduces the world-readability, and the inconsistency with token_manager's existing 0o600 writes is the tell.

Out of scope (follow-up)

The /tmp and cwd fallbacks in get_writable_config_path() remain. They're far less dangerous now (files are 0o600), but silently writing credentials to a predictable /tmp path or cwd is worth revisiting separately.

Tests

New regression tests assert 0o600 files and 0o700 dirs, and that shared fallback dirs are not globally tightened. All fail on the pre-fix code and pass after. Full settings/token suite (39 tests) green; ruff + mypy clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Hardened local configuration persistence by enforcing owner-only permissions for the config directory (0o700) and settings file (0o600), and by writing settings atomically for safer updates.
    • Strengthened credential storage by ensuring the secure credentials directory is set to owner-only permissions (0o700) after creation (best-effort, without blocking token usage).
  • Tests

    • Added regression coverage for configuration and credential file/directory permissions with platform-aware skipping on Windows.

Credentials stored at rest were left world-readable on multi-user hosts:

- TokenManager._get_secure_storage_path() documented its credential dir as
  mode 0o700 but created it via mkdir() with default perms (0o755), leaving
  the Fernet secret.key and encrypted tokens.enc in a traversable dir.
- Settings.dump() persisted tool_repository_password (plaintext) to
  settings.json via open("w"), producing a 0o644 file, and created the
  config dir at 0o755 — despite the sibling token_manager already writing
  secrets atomically at 0o600.

Fixes:
- TokenManager: chmod the credential dir to 0o700 after mkdir (robust against
  umask and pre-existing dirs).
- Settings: write settings.json atomically at 0o600 (mkstemp + chmod +
  os.replace) and chmod the dedicated config dir to 0o700. The /tmp and cwd
  fallback parents are deliberately not chmod'd; the 0o600 file mode protects
  the credential there.

Adds regression tests asserting 0o600 files and 0o700 dirs, and that shared
fallback dirs are not globally tightened.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 19, 2026 06:24

@corridor-security corridor-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: This PR tightens credential-at-rest permissions by enforcing owner-only modes on settings files and credential directories and using atomic JSON writes.

Risk: Low risk. No exploitable security vulnerabilities were identified in the added code; the changes reduce local disclosure risk without introducing a new public attack surface or weakening authentication or authorization controls.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ac4dff8-0cd7-4163-9770-aab9f297b7e8

📥 Commits

Reviewing files that changed from the base of the PR and between 714cd11 and df4495d.

📒 Files selected for processing (1)
  • lib/crewai-core/src/crewai_core/token_manager.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/crewai-core/src/crewai_core/token_manager.py

📝 Walkthrough

Walkthrough

settings.py adds two internal helpers—_ensure_dir_mode (chmod 0o700 on dedicated config directories) and _write_secure_json (atomic temp-file write with 0o600 mode)—and wires them into get_writable_config_path, Settings.__init__, and Settings.dump. token_manager.py adds an explicit chmod(0o700) call after credential directory creation. Both behaviors are covered by new POSIX-only test classes skipped on Windows.

Changes

POSIX Permission Enforcement for Config and Credential Files

Layer / File(s) Summary
settings.py permission helpers and call sites
lib/crewai-core/src/crewai_core/settings.py
Adds os import, _ensure_dir_mode (restricts dedicated config dirs to 0o700, skips shared/temp/CWD paths), and _write_secure_json (atomic write via temp file + os.replace with 0o600 mode). get_writable_config_path(), Settings.__init__(), and Settings.dump() are each updated to call the appropriate helper.
TokenManager credential directory chmod
lib/crewai-core/src/crewai_core/token_manager.py
Adds storage_path.chmod(0o700) with OSError suppression in _get_secure_storage_path() to enforce directory mode independently of umask.
POSIX permission regression tests
lib/cli/tests/test_config.py, lib/cli/tests/test_token_manager.py
TestSettingsFilePermissions asserts settings.json is 0o600, dedicated config dir is 0o700, and _ensure_dir_mode does not chmod temp dir parents. TestSecureStoragePathPermissions verifies credential dirs are created and corrected to 0o700 under a permissive umask. Both suites skip on Windows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: enforcing owner-only permissions on credential files to address security vulnerabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/credential-file-permissions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread lib/crewai-core/src/crewai_core/settings.py Fixed
Comment thread lib/crewai-core/src/crewai_core/token_manager.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens credential-at-rest handling by enforcing owner-only permissions for the token credential directory and the persisted settings file, aligning behavior with the existing secure-write approach used elsewhere in the codebase.

Changes:

  • Enforce 0o700 on the TokenManager credential directory after creation (umask- and pre-existing-dir-safe).
  • Write settings.json atomically and enforce 0o600 for the file and 0o700 for the dedicated config directory (while avoiding chmod on shared fallback dirs).
  • Add regression tests asserting the restrictive modes and ensuring shared fallback directories aren’t chmod’d.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
lib/crewai-core/src/crewai_core/token_manager.py Tightens credential directory permissions to match documented 0o700.
lib/crewai-core/src/crewai_core/settings.py Adds secure directory-mode enforcement and atomic 0o600 settings writes.
lib/cli/tests/test_token_manager.py Adds permission regression tests for TokenManager storage directory.
lib/cli/tests/test_config.py Adds permission regression tests for settings file/dir and shared fallback behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +46 to +57
def _write_secure_json(path: Path, data: dict[str, Any]) -> None:
"""Atomically write ``data`` as JSON to ``path`` with owner-only (0o600) mode."""
fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=f".{path.name}.")
try:
with os.fdopen(fd, "w") as f:
json.dump(data, f, indent=4)
os.chmod(tmp, 0o600)
os.replace(tmp, path)
except BaseException:
if os.path.exists(tmp):
os.unlink(tmp)
raise
Comment thread lib/cli/tests/test_config.py
Comment on lines +291 to +293
class TestSecureStoragePathPermissions(unittest.TestCase):
"""Test that the credential directory is created with restrictive permissions."""

theCyberTech and others added 2 commits June 19, 2026 14:42
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants