Trusted Folders Security
The trusted folders system prevents untrusted project-local configuration, tools, agents, and skills from being loaded automatically.
Purpose
When a user navigates to a project directory that contains .vibe/ configuration, tools, agents, or skills, there is a risk that malicious project-local content could be loaded and executed. The trusted folders system ensures that users must explicitly approve each directory before its local content is used.
TrustedFoldersManager
File: vibe/core/trusted_folders.py:22
class TrustedFoldersManager:
def __init__(self) -> None:
self._file_path = TRUSTED_FOLDERS_FILE.path
self._trusted: list[str] = []
self._untrusted: list[str] = []
self._load()A singleton instance is created at module level:
# trusted_folders.py:83
trusted_folders_manager = TrustedFoldersManager()Trust States
| State | is_trusted() return |
Behavior |
|---|---|---|
| Trusted | True |
Local config/tools/agents/skills loaded |
| Untrusted | False |
Only global config used |
| Unknown | None |
Triggers trust dialog in interactive mode |
Key Methods
| Method | Line | Purpose |
|---|---|---|
is_trusted(path) |
58 | Check trust state (True/False/None) |
add_trusted(path) |
66 | Mark path as trusted, remove from untrusted |
add_untrusted(path) |
74 | Mark path as untrusted, remove from trusted |
_load() |
32 | Load state from TOML file |
_save() |
49 | Persist state to TOML file |
Path Normalization
All paths are normalized via expanduser().resolve() before storage:
def _normalize_path(self, path: Path) -> str:
return str(path.expanduser().resolve())Storage
Trust decisions are persisted in ~/.vibe/trusted_folders.toml:
trusted = [
"/Users/user/projects/my-project",
"/Users/user/projects/another-project",
]
untrusted = [
"/Users/user/Downloads/suspicious-repo",
]Trustable Content Detection
Function: has_trustable_content() at trusted_folders.py:13
TRUSTABLE_FILENAMES = ["AGENTS.md", "VIBE.md", ".vibe.md"]
def has_trustable_content(path: Path) -> bool:
if (path / ".vibe").exists():
return True
for name in TRUSTABLE_FILENAMES:
if (path / name).exists():
return True
return FalseA directory is considered to have trustable content if it contains:
- A
.vibe/directory - An
AGENTS.mdfile - A
VIBE.mdfile - A
.vibe.mdfile
Trust Check Flow
Function: check_and_resolve_trusted_folder() at entrypoint.py:103
check_and_resolve_trusted_folder() [entrypoint.py:103-135]
│
├─1─► Check if current directory has trustable content
│ has_trustable_content(Path.cwd())
│ If no trustable content: return (nothing to check)
│
├─2─► Check existing trust state
│ trusted_folders_manager.is_trusted(cwd)
│ │
│ ├── True: return (already trusted)
│ └── False: return (already untrusted)
│
├─3─► If None (unknown): show trust dialog
│ TrustFolderDialog(cwd)
│ │
│ ├── User selects "Trust": add_trusted(cwd)
│ └── User selects "Don't Trust": add_untrusted(cwd)
│
└─4─► Only runs in interactive mode
Skipped for programmatic (--prompt) mode
Integration Points
Config Path Resolution
File: vibe/core/paths/config_paths.py
def _resolve_config_path(basename: str, type: Literal["file", "dir"]) -> Path:
cwd = Path.cwd()
is_folder_trusted = trusted_folders_manager.is_trusted(cwd)
if not is_folder_trusted:
return VIBE_HOME.path / basename # Fall back to global
# Only check local .vibe/ if trusted
if type == "file":
if (candidate := cwd / ".vibe" / basename).is_file():
return candidate
elif type == "dir":
if (candidate := cwd / ".vibe" / basename).is_dir():
return candidate
return VIBE_HOME.path / basenameTool Discovery
Function: resolve_local_tools_dir() at config_paths.py:34
def resolve_local_tools_dir(dir: Path) -> Path | None:
if not trusted_folders_manager.is_trusted(dir):
return None # Block local tools from untrusted dirs
if (candidate := dir / ".vibe" / "tools").is_dir():
return candidate
return NoneSkill Discovery
Function: resolve_local_skills_dir() at config_paths.py:42
Same pattern as tools — returns None for untrusted directories.
Agent Discovery
Function: resolve_local_agents_dir() at config_paths.py:50
Same pattern as tools — returns None for untrusted directories.
ConfigPath and Locking
File: vibe/core/paths/config_paths.py
ConfigPath extends GlobalPath with a locking mechanism:
_config_paths_locked: bool = True
class ConfigPath(GlobalPath):
@property
def path(self) -> Path:
if _config_paths_locked:
raise RuntimeError("Config path is locked")
return super().pathConfig paths are locked by default and unlocked only after the trust check:
main() [entrypoint.py:137]
│
├── check_and_resolve_trusted_folder() # Trust check first
└── unlock_config_paths() # Then unlock
This prevents any code from accidentally resolving config paths before the trust decision is made.
Execution Sequence
vibe entrypoint [entrypoint.py:137]
│
├─1─► Parse arguments
│
├─2─► Change workdir if --workdir specified
│
├─3─► If interactive mode:
│ check_and_resolve_trusted_folder() [entrypoint.py:151]
│ ├── Detect trustable content
│ ├── Show dialog if unknown
│ └── Record decision
│
├─4─► unlock_config_paths() [entrypoint.py:152]
│ Allow ConfigPath instances to resolve
│
└─5─► run_cli(args) [entrypoint.py:156]
Config loading now respects trust decisions
Security Model
The trust system ensures:
- No silent loading: Project-local config/tools/agents/skills are never loaded without explicit user consent
- Persistent decisions: Trust decisions are saved, so the user is only asked once per directory
- Home directory exempt: The home directory itself is never treated as trustable
- Path normalization: All paths are
expanduser().resolve()before storage - Graceful degradation: If trust state is unknown or untrusted, only global config is used
Source File References
| File | Key Lines | Description |
|---|---|---|
trusted_folders.py:10 |
TRUSTABLE_FILENAMES |
Files that trigger trust check |
trusted_folders.py:13-19 |
has_trustable_content() |
Detect trustable content |
trusted_folders.py:22-80 |
TrustedFoldersManager |
Trust state management |
trusted_folders.py:58-64 |
is_trusted() |
Check trust state |
trusted_folders.py:83 |
trusted_folders_manager |
Singleton instance |
config_paths.py:12-17 |
ConfigPath |
Trust-locked path resolver |
config_paths.py:20-31 |
_resolve_config_path() |
Trust-aware config resolution |
config_paths.py:34-39 |
resolve_local_tools_dir() |
Trust-aware tool discovery |
config_paths.py:42-47 |
resolve_local_skills_dir() |
Trust-aware skill discovery |
config_paths.py:50-55 |
resolve_local_agents_dir() |
Trust-aware agent discovery |
config_paths.py:58-60 |
unlock_config_paths() |
Unlock config path resolution |
entrypoint.py:103-135 |
check_and_resolve_trusted_folder() |
Trust dialog flow |