Welcome back! In Chapter 5: Command Execution Wrapping, we learned how to wrap a command in a "Biohazard Box" so it couldn't touch your personal files while it was running.
But what happens when the command is finished?
Imagine a demolition crew (the command) finishes working in your house. They didn't break anything while they were there because you were watching them. However, just before they left, they unlocked your back window and left it open.
They didn't steal anything yet. But next time you walk past that window, a thief could climb in.
In the world of sandboxing, this is a common attack vector:
.git folder.git command outside the sandbox, your Git tool sees that fake folder, gets confused, and accidentally executes malicious code hidden inside it.Security Scrubbing is the cleanup crew that sweeps the room immediately after the command finishes. If it finds any "unlocked windows" (traps), it destroys them before you have a chance to stumble into them.
Scenario: A sandboxed script creates a folder named .git and puts a file named HEAD inside it.
Goal: As soon as the script finishes, the Sandbox Manager must detect that this HEAD file shouldn't be there and delete it immediately.
Git is complex. If a folder contains specific files (like HEAD, objects, and refs), Git thinks, "Oh, this is a repository!" even if there is no .git folder. Attackers use this to trick your computer. The specific cleaner we use is called scrubBareGitRepoFiles.
We don't scan the entire hard drive after every command (that would be too slow). Instead, before the command runs, we make a list of "Forbidden Files."
HEAD doesn't exist right now, add it to the Kill List."HEAD appears during the command, we know it's new, so we delete it."
We need a trigger to run the cleaning logic. This is done via cleanupAfterCommand, which runs automatically every time the sandbox shuts down a process.
This process is entirely automated. The rest of the application simply calls one method when it's done running a tool.
// Inside the main application loop
import { SandboxManager } from './sandbox-adapter';
try {
// Run the potentially dangerous command
await executeCommand('npm install suspicious-package');
} finally {
// ALWAYS run the cleanup, even if the command crashed
SandboxManager.cleanupAfterCommand();
}
Explanation:
The finally block ensures that the cleanup crew runs no matter what. If the malicious script crashed your terminal, we still scrub the files to ensure safety.
The cleanup process is actually split into two parts: Planning (before the command) and Execution (after the command).
Let's look at how this is implemented in sandbox-adapter.ts.
This happens inside convertToSandboxRuntimeConfig (which we discussed in Chapter 3). We check if these dangerous files already exist.
// Define the files that make up a "Bare Git Repo"
const bareGitRepoFiles = ['HEAD', 'objects', 'refs', 'hooks', 'config'];
// A global list to remember what to delete later
const bareGitRepoScrubPaths: string[] = [];
for (const gitFile of bareGitRepoFiles) {
const p = resolve(cwd, gitFile); // e.g., /project/HEAD
try {
statSync(p); // Check if it exists
// If it exists, we make it Read-Only (handled elsewhere)
} catch {
// If it DOES NOT exist, we mark it for potential cleanup
bareGitRepoScrubPaths.push(p);
}
}
Explanation:
We use statSync to "look" at the file system. If the try block fails (executes the catch), it means the file is missing. If it's missing now, but appears later, it's an intruder. We add it to bareGitRepoScrubPaths.
This runs inside cleanupAfterCommand.
function scrubBareGitRepoFiles(): void {
// Loop through the Kill List we made earlier
for (const p of bareGitRepoScrubPaths) {
try {
// Forcefully remove the file/folder
rmSync(p, { recursive: true });
// Log it for debugging
logForDebugging(`[Sandbox] scrubbed planted bare-repo file: ${p}`);
} catch {
// If the file still doesn't exist, that's good! Do nothing.
}
}
}
Explanation:
This function is ruthless. It iterates through the list of "forbidden new files." If the malicious command created them, rmSync deletes them instantly. If the command didn't create them, rmSync fails silently (inside the catch), and we move on.
The public SandboxManager object combines the standard cleanup with our custom Git scrubbing.
export const SandboxManager: ISandboxManager = {
// ... other methods ...
cleanupAfterCommand: (): void => {
// 1. Run standard cleanup (temp files, network sockets)
BaseSandboxManager.cleanupAfterCommand();
// 2. Run our custom anti-trap logic
scrubBareGitRepoFiles();
},
}
Explanation:
This ensures that when the application calls cleanupAfterCommand, both the generic cleanup (from the core library) and our specific "Bare Git Repo" protection run together.
Security Scrubbing is the final layer of defense. While the Sandbox Wrapper prevents the command from accessing existing sensitive files, the Scrubber prevents the command from creating new dangerous files that could hurt you later.
By identifying potential traps (like fake Git metadata) before the command runs, and sweeping for them after it finishes, we ensure the workspace remains safe for the user.
We have now covered the entire security lifecycle:
However, all of this happens silently in the background. How do we explain to the user what is happening without overwhelming them with technical logs?
Next Chapter: UI Presentation Utilities
Generated by Code IQ