Welcome to the final chapter of our File Persistence tutorial series!
In the previous chapter, Delta Scanning, we learned how the system identifies which files have changed. We successfully gathered a list of "new" files.
However, having a list of files isn't enough. Just because a file exists doesn't mean it's safe to upload.
Imagine you have a designated "Safe Box" (the outputs directory) where Claude is allowed to write files.
Now, imagine a user writes a script that does this:
# Create a file that is technically inside the box...
# ...but points to a secret file outside the box!
ln -s /etc/passwords ./outputs/my_secret_link
Or perhaps they create a file path like this:
./outputs/../../system_config.txt
If we blindly upload these, we might accidentally expose sensitive system files or get stuck in infinite loops. We need Security Sanitization.
This abstraction acts like Border Control at an airport. Even if you have a ticket (the file was modified), you still have to pass through security.
The Sanitizer enforces two strict rules:
.. (dot-dot) to climb out of the designated folder.Let's visualize how the list of files from the Scanner is processed before it reaches the Uploader.
The sanitization logic happens inside filePersistence.ts, right after we get the list from the scanner.
Computers usually deal with "Absolute Paths" (the full address, e.g., C:\Users\Name\Project\outputs\file.txt).
To check if a file is trying to escape, we need to convert it to a "Relative Path" (where is it relative to the output folder?).
// From filePersistence.ts
// Transform absolute paths into relative ones
const filesToProcess = modifiedFiles
.map(filePath => ({
path: filePath,
// Calculate distance from "outputsDir" to the file
relativePath: relative(outputsDir, filePath),
}))
Explanation:
The relative function does the math.
outputsDir is /projectfilePath is /project/data/file.txtrelativePath becomes data/file.txt.
In file systems, .. means "Go up one folder." If a relative path starts with .., it means the file is located above or outside our current folder.
We filter the list to remove these escape attempts.
// Filter out any paths that try to escape
.filter(({ relativePath }) => {
// If the path starts with "..", it is outside our sandbox
if (relativePath.startsWith('..')) {
logDebug(`Skipping file outside outputs directory: ${relativePath}`)
return false
}
return true
})
Explanation:
subfolder/image.png (Stays inside).../windows/system32/config (Tries to leave the output folder).
The code simply returns false to drop these files from the list.
You might wonder, "Where is the Symlink check?"
If you recall from Delta Scanning, the scanner proactively ignores symbolic links right at the source:
// From outputsScanner.ts (Recap)
if (entry.isSymbolicLink()) {
continue // Skip immediately
}
This effectively creates a multi-layered security strategy:
Once the list has been sanitized, we are left with filesToProcess. These files are guaranteed to be:
Only now does the system hand them over to the API for uploading.
// Finally, upload the clean list
const results = await uploadSessionFiles(
filesToProcess,
config,
DEFAULT_UPLOAD_CONCURRENCY,
)
Congratulations! You have completed the File Persistence tutorial series.
Let's review the journey of a file in this system:
By chaining these five concepts together, the filePersistence module ensures that user work is saved reliably, securely, and automatically, without ever accidentally exposing sensitive system data.
Thank you for reading!
Generated by Code IQ