AI & Agents

How to Resolve Paths Safely in C++ with std::filesystem::absolute

When building AI agent workspaces, resolving untrusted file paths is a critical security boundary. This guide explains why std::filesystem::absolute fails to prevent directory traversal and how to use std::filesystem::canonical to secure path resolution.

Fast.io Editorial Team 8 min read
Securing file pathways in multi-agent sandboxes.

Securing File System Boundaries in AI Agent Workspaces

When multiple AI agents work in a shared C++ environment, validating the files they access is a critical boundary. If you rely on simple string prefix checks to lock agents inside a specific sandbox folder, a malicious or malfunctioning agent can easily break out using symbolic links or directory traversal.

Traditional human-centric file systems rely on the OS user permissions or desktop sync clients to govern file access. When coordinating developer agents across tools like Claude Code, Cursor, or OpenClaw, the application code itself must enforce boundaries on path operations. The standard way to resolve these paths is using the standard filesystem library introduced in the C++17 standard. However, standard documentation often omits the security pitfalls of path resolution, focusing entirely on syntax.

If an agent is instructed to write to a workspace, it might construct a path containing relative dot-dot components or reference a symbolic link pointing outside the sandbox directory. Checking whether the input path string starts with the sandbox root is insufficient. The path must be resolved to its true physical location before evaluation. This is where developers often reach for std::filesystem::absolute, unaware that it leaves the system vulnerable to sandbox escape.

Building a secure sandbox requires understanding how paths are interpreted by the operating system and standard library. The core issue lies in the difference between lexical path manipulation, which modifies path strings based on simple rules, and physical path resolution, which queries the actual filesystem state. Relying on lexical checks alone allows paths that appear safe to bypass boundary checks, resulting in unauthorized file access. Without a physical validation layer, any tool designed to coordinate agents risks exposing host resources to unintended manipulation.

Why std::filesystem::absolute Fails in Sandbox Security

The std::filesystem::absolute function in C++ returns the absolute path of a given file system path, resolving relative components without checking if the file actually exists. It acts as a syntactic transformer, prepending the current working directory as returned by std::filesystem::current_path() to any path that is not already absolute.

Because this resolution is purely syntactic, std::filesystem::absolute does not interact with the underlying physical filesystem. It does not resolve symbolic links, and it does not check if the path exists on disk.

Consider a C++ application running an agent sandbox with a root directory at /sandbox/workspace. The agent attempts to read a file by passing the relative path ../etc/passwd. If you use std::filesystem::absolute, the function resolves this relative path to /sandbox/workspace/../etc/passwd. If your security check simply tests whether this resolved path string begins with the prefix /sandbox/workspace, it will fail because the string literally contains the prefix. When the path is subsequently passed to a file stream like std::ifstream, the operating system resolves the dot-dot components, escaping the sandbox to access /etc/passwd.

Even if you call .lexically_normal() on the path returned by std::filesystem::absolute to resolve the dot-dot components, you remain vulnerable to symbolic link attacks. If an agent creates a symbolic link inside the sandbox pointing to a folder outside the sandbox, std::filesystem::absolute will not resolve that link. It will treat the link as a normal directory path.

Here is a C++ code snippet demonstrating this behavior:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    // Current working directory is /sandbox/workspace
    fs::path sandbox_root = "/sandbox/workspace";
    fs::path untrusted_input = "../etc/passwd";
    
    // std::filesystem::absolute performs a syntactic prepend
    fs::path resolved_path = fs::absolute(untrusted_input);
    
    std::cout << "Input: " << untrusted_input << std::endl;
    std::cout << "Absolute: " << resolved_path << std::endl;
    std::cout << "Normalized: " << resolved_path.lexically_normal() << std::endl;
    
    return 0;
}

Running this code shows that std::filesystem::absolute simply prepends the current directory. If you check the prefix of the raw absolute path, the check is bypassed. If you check the prefix of the lexically normalized path, it resolves to /etc/passwd, which fails the prefix check, but as we will see, lexical normalization still cannot handle symbolic links.

How to Resolve Physical Paths with std::filesystem::canonical

To safely validate paths in a sandbox, you must resolve symbolic links and physical filesystem locations. This requires std::filesystem::canonical.

Unlike std::filesystem::absolute, std::filesystem::canonical resolves all symbolic links, current directory elements (.), and parent directory elements (..) to produce a unique, absolute canonical path. To achieve this, it queries the operating system filesystem, which means the target path must exist. If the path does not exist, std::filesystem::canonical throws a std::filesystem::filesystem_error or sets a std::error_code depending on the overload you choose.

If your agent needs to write a new file, the file will not exist yet, causing std::filesystem::canonical to fail. For these scenarios, the C++17 standard provides std::filesystem::weakly_canonical. This function resolves symbolic links and dot-dot components for the portion of the path that does exist, and normalizes the remaining non-existent portion syntactically.

Here is how the three functions compare:

  • std::filesystem::absolute. Performs syntactic path resolution, prepending the current working directory if relative. Does not resolve symbolic links, . or .. segments. Does not check if the path exists.
  • std::filesystem::canonical. Resolves all symbolic links, . and .. segments to their physical locations. Requires the target path to exist. Throws an exception or sets an error code if the path is missing.
  • std::filesystem::weakly_canonical. Resolves symbolic links and dot-dot components for the portion of the path that exists, then normalizes the remainder syntactically. Does not require the entire path to exist, making it ideal for new file creation.

Here is a secure path validation utility implemented in C++ using std::filesystem::canonical and std::filesystem::weakly_canonical:

#include <iostream>
#include <filesystem>
#include <algorithm>
#include <system_error>

namespace fs = std::filesystem;

bool is_path_safe(const fs::path& base_dir, const fs::path& user_input) {
    std::error_code ec;
    
    // 1. Resolve base directory to its absolute canonical path
    fs::path base_resolved = fs::canonical(base_dir, ec);
    if (ec) {
        // Base directory must exist and be accessible
        return false;
    }
    
    // 2. Resolve user input path
    // Combine base and user input, then resolve using weakly_canonical
    // in case the file does not exist yet.
    fs::path requested_path = fs::weakly_canonical(base_resolved / user_input, ec);
    if (ec) {
        return false;
    }
    
    // 3. Verify that the requested path starts with the base path
    // We compare path components using mismatch
    auto [base_it, req_it] = std::mismatch(
        base_resolved.begin(), base_resolved.end(),
        requested_path.begin(), requested_path.end()
    );
    
    // If the mismatch iterator for the base path reached the end,
    // it means requested_path starts with base_resolved
    return base_it == base_resolved.end();
}

int main() {
    fs::path sandbox = "/tmp/sandbox";
    fs::create_directories(sandbox);
    
    // Test safe path
    std::cout << "Safe path: " << (is_path_safe(sandbox, "data.txt") ? "YES" : "NO") << std::endl;
    
    // Test directory traversal
    std::cout << "Traversal path: " << (is_path_safe(sandbox, "../etc/passwd") ? "YES" : "NO") << std::endl;
    
    return 0;
}

By resolving the base directory and the requested path to their physical locations, you ensure that symbolic links pointing outside the sandbox are resolved to their true targets, allowing your prefix comparison to catch the escape attempt.

Fastio features

Secure your agent file operations in shared workspaces

Deploy a dedicated workspace for your AI agents using our remote MCP endpoint, complete with persistent storage and automatic version history. Every organization starts with a 14-day free trial.

Even when using std::filesystem::canonical, a sandbox is vulnerable to Time-of-Check to Time-of-Use (TOCTOU) exploits. This race condition occurs when an agent validates a path, but before the file is actually opened, a concurrent process or another agent swaps the file or parent directory with a symbolic link.

To mitigate TOCTOU vulnerabilities, you should keep path validation and file access as atomic as possible. On Linux systems, you can use the openat family of system calls, or the newer openat2 system call introduced in Linux kernel 5.6. The openat2 call supports the RESOLVE_BENEATH flag, which instructs the kernel to reject path resolution if any component (including symbolic links) attempts to escape the directory file descriptor passed as the base.

Another strategy is to disable symbolic links entirely within directories modified by agents. You can check if a path is a symbolic link using std::filesystem::is_symlink before reading or writing. If the file is a symbolic link, you can immediately terminate the operation and log the security event.

For teams coordinating multiple agents, managing these low-level file system security checks on local hardware can become complex. An alternative is to move agent file operations out of the local OS environment. Using a remote, isolated agent storage solution prevents agents from accessing the host file system.

Instead of writing to the host OS, agents can interact with remote workspaces over the Model Context Protocol (MCP). Because the agent does not run in the same OS namespace as the storage, directory traversal and symbolic link exploits on the host file system are physically impossible. The agent only sees the virtual workspace exposed by the MCP server, keeping the host secure. This isolation ensures that even if an agent runs malicious code, it cannot break out of its containerized workspace.

How to Coordinate Files Safely in Multi-Agent Environments

When multiple developer agents collaborate, they need a secure coordination layer to prevent conflicts, overwrite errors, and security escapes. Managing these boundaries locally requires implementing complex path sanitization utilities and kernel-level sandbox tools.

Using an intelligent workspace platform like Fast.io simplifies this coordination. Fast.io serves as a neutral ground where agents and humans share the same workspaces. You can run different agents, including Claude Code, Cursor, or OpenClaw, side by side. Instead of mapping a local directory to a sync client like Google Drive or Dropbox, which introduces file conflict and path traversal risks, agents connect directly via the Fast.io MCP server.

The MCP server handles authentication in-band. For config blocks requiring token authorization on every request, you can use the secure endpoint https://mcp.fast.io/mcp/key with an Authorization: Bearer <api-key> header as documented in the MCP server documentation.

Because the storage is managed off-host, agents interact with files using safe API and MCP primitives. Fast.io provides version history for every file, ensuring that if two agents attempt to edit the same file, prior versions can be restored. Additionally, an append-only audit log records every file change, while granular permissions at the organization, workspace, folder, and file level ensure agents only access authorized content.

When an agent completes a task, it can transfer ownership of the workspace or files to a human team member. This ensures the output is safely delivered to the human, who can then create a branded share link to send the deliverables to clients.

Every organization starts with a 14-day free trial, which requires a credit card. Subscriptions are structured to fit different team sizes, with the Starter plan at 29 USD/mo, the Business plan at 99 USD/mo, and the Growth plan at 299 USD/mo. Details on all subscription features are available on the Fast.io pricing page. By shifting file coordination to a managed, intelligent workspace, you eliminate the risks of C++ path resolution bugs and secure your development pipelines.

Frequently Asked Questions

What is the difference between std::filesystem::absolute and canonical?

The std::filesystem::absolute function performs syntactic path resolution by prepending the current working directory to a relative path without checking if the path exists or resolving symbolic links. The std::filesystem::canonical function resolves all symbolic links, current directory elements (.), and parent directory elements (..) to produce a physical absolute path, and it requires the target path to exist in the filesystem.

Does std::filesystem::absolute check if a file exists?

No, std::filesystem::absolute does not check for the existence of a file or directory. It operates entirely as a syntactic transformation on the path string, appending the current working directory to any relative path. To verify if a path actually exists on the filesystem, you must use std::filesystem::exists or convert it using std::filesystem::canonical.

How to get absolute path in C++?

You can obtain an absolute path in C++ by using std::filesystem::absolute for syntactic resolution, or std::filesystem::canonical to resolve symbolic links and verify the path exists. For cases where the path might not exist yet, std::filesystem::weakly_canonical should be used to resolve the existing portions of the path and normalize the rest.

Related Resources

Fastio features

Secure your agent file operations in shared workspaces

Deploy a dedicated workspace for your AI agents using our remote MCP endpoint, complete with persistent storage and automatic version history. Every organization starts with a 14-day free trial.