How the code-review-graph Refactor Tool Handles Rename Previews and Apply Operations Securely
The refactor tool implements a two-phase workflow that separates preview generation from destructive changes, using sandboxed webviews, temporary staging directories with SHA-256 checksums, and atomic file operations to prevent data corruption and workspace escape vulnerabilities.
The code-review-graph extension provides sophisticated refactoring capabilities that ensure rename previews and apply operations are handled securely through a defense-in-depth architecture. By separating read-only analysis from write operations and confining all filesystem interactions to a temporary staging area, the tool protects against path traversal attacks, partial writes, and accidental data loss while maintaining an interactive user experience.
The Two-Phase Refactor Architecture
The rename workflow is explicitly divided into distinct preview and apply phases to ensure user intent is verified before any filesystem modification occurs.
Phase 1: Building the Rename Preview
During the preview phase, the tool performs a complete scan of the code graph to locate every reference to the symbol targeted for renaming. It constructs a rename transaction entirely in memory, producing a detailed report that lists affected files, line numbers, and the exact text changes that would occur. No file on disk is touched during this phase.
Security measures in the preview phase include:
- In-memory processing: All analysis data remains in RAM; the preview never writes to the filesystem
- Path sanitization: Any path not under the VS Code workspace root is discarded immediately, preventing path-traversal attacks
- Sandboxed rendering: The preview report displays through VS Code’s Webview API in a sandboxed iframe, isolating the UI from the extension host
Phase 2: Secure Apply Operations
When the user confirms the preview by clicking Apply, the tool transitions to the destructive phase. Rather than modifying original files directly, it creates a temporary staging directory using fs.mkdtemp, writes changes to copies in this isolated location, performs cryptographic verification, and then executes atomic renames.
This phase implements several integrity guarantees:
- Staging isolation: All writes occur in the temporary directory until verification passes
- Checksum validation: SHA-256 hashes verify file integrity before commitment
- Atomic replacement:
fs.renameSyncensures that changes appear either completely or not at all, eliminating half-finished states - Workspace confinement: All file-system actions resolve paths relative to
vscode.workspace.getWorkspaceFolder, preventing access outside the project boundary
Security Mechanisms in Detail
Workspace Confinement and Path Sanitization
The extension strictly scopes all file operations to the current workspace root. Before processing any file path, the system validates that it falls within the directory returned by vscode.workspace.getWorkspaceFolder. Paths that resolve outside this boundary are rejected immediately, ensuring the extension cannot access system files or other projects.
Staging, Checksums, and Atomic Renames
The staging implementation in src/features/refactor.ts follows a defensive sequence:
- Temporary directory creation: Uses
fs.promises.mkdtempwith acrg-prefix to create an isolated working area - Content staging: Writes proposed changes to temporary copies rather than originals
- Cryptographic verification: Computes SHA-256 checksums of staged content and compares them against expected values before proceeding
- Atomic commit: Uses
fs.promises.renameto move staging files over originals, leveraging POSIX atomicity guarantees
If checksum verification fails at any point, the operation aborts completely and VS Code displays an error dialog, leaving original files untouched.
Isolated Preview Rendering
The preview UI renders inside a VS Code webview, which executes in a sandboxed iframe with restricted privileges. Communication between the webview and the extension host occurs exclusively through the vscode.postMessage API, ensuring that malicious content in the preview cannot access the Node.js filesystem APIs or extension context.
Core Implementation Files
CLI Wrapper (src/backend/cli.ts)
The CliWrapper class provides the interface between the VS Code extension and the command-line refactor tool. It launches the refactor sub-command (code-review-graph refactor …) within a VS Code progress notification and standardizes return values as { success, stdout, stderr } objects. This abstraction ensures consistent error handling across both preview and apply operations.
Transaction Builder (src/features/refactor.ts)
This module contains the transaction builder that walks the code graph to collect rename candidates and construct the preview JSON. It also implements the critical staging logic: writing to temporary copies, validating SHA-256 checksums, and executing the atomic replace sequence. This file coordinates with src/features/blastRadius.ts to determine the impact radius of a rename, ensuring only relevant files enter the transaction.
Webview Interface (src/views/graphWebview.ts)
Responsible for rendering the preview UI, this file creates the sandboxed webview panel and manages message passing. When the user clicks Apply, it forwards the command to the extension host via vscode.postMessage, triggering the secure apply workflow without exposing filesystem paths to the renderer.
Practical Code Examples
Triggering a Rename Preview
The following TypeScript demonstrates how the extension initiates a dry-run preview through the command palette:
// Triggered by VS Code command: codeReviewGraph.refactorRename
async function runRenamePreview(oldName: string, newName: string) {
const cli = new CliWrapper(); // from src/backend/cli.ts
const args = ['refactor', 'rename', '--dry-run', oldName, newName];
const result = await cli.exec(args, vscode.workspace.rootPath!);
if (result.success) {
// Send JSON to webview for display
graphWebview.postMessage({
type: 'preview',
payload: JSON.parse(result.stdout)
});
} else {
vscode.window.showErrorMessage(result.stderr);
}
}
Handling User Confirmation and Apply
This code runs inside the webview's message handler to securely forward the apply command:
// Inside src/views/graphWebview.ts message handler
window.addEventListener('message', async (event) => {
if (event.data.type === 'applyRename') {
const { oldName, newName } = event.data.payload;
const cli = new CliWrapper();
const args = ['refactor', 'rename', oldName, newName]; // no --dry-run flag
const result = await cli.exec(args, vscode.workspace.rootPath!);
if (result.success) {
vscode.window.showInformationMessage('Rename applied successfully');
} else {
vscode.window.showErrorMessage(`Rename failed: ${result.stderr}`);
}
}
});
Core Staging and Verification Logic
The following simplified implementation from src/features/refactor.ts demonstrates the checksum verification and atomic replacement strategy:
// Inside src/features/refactor.ts
async function applyRenameTransaction(changes: RenameChange[]) {
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'crg-'));
const hashes: Record<string, string> = {};
// 1. Write staged copies and compute checksums
for (const { file, newContent } of changes) {
const stagingPath = path.join(tmpDir, path.basename(file));
await fs.promises.writeFile(stagingPath, newContent);
hashes[stagingPath] = crypto.createHash('sha256')
.update(newContent)
.digest('hex');
}
// 2. Verify checksums before committing
for (const { file } of changes) {
const stagingPath = path.join(tmpDir, path.basename(file));
const expected = hashes[stagingPath];
const actual = crypto.createHash('sha256')
.update(await fs.promises.readFile(stagingPath))
.digest('hex');
if (expected !== actual) {
throw new Error(`Checksum mismatch for ${file}`);
}
}
// 3. Atomic replace
for (const { file } of changes) {
const stagingPath = path.join(tmpDir, path.basename(file));
await fs.promises.rename(stagingPath, file); // atomic on POSIX
}
}
Summary
- Two-phase separation ensures preview operations are read-only and apply operations require explicit user confirmation
- SHA-256 checksums validate file integrity between staging and commitment, preventing corruption from disk errors or race conditions
- Atomic rename operations guarantee that file updates appear completely or not at all, eliminating partial modification states
- Workspace root scoping combined with path sanitization prevents the extension from accessing files outside the project directory
- Sandboxed webview rendering isolates the preview UI from filesystem APIs, protecting against injection attacks
Frequently Asked Questions
How does the tool prevent modification of files outside the workspace?
The extension retrieves the workspace root using vscode.workspace.getWorkspaceFolder and validates every target path against this boundary before inclusion in the rename transaction. Any path resolving outside the workspace is discarded during the preview generation phase, effectively preventing path-traversal attacks.
What happens if the extension crashes during the apply phase?
Because the tool writes all changes to a temporary staging directory first and only modifies originals after successful checksum verification, a crash at any point leaves the original files untouched. The temporary directory uses the crg- prefix and is created via fs.mkdtemp, ensuring that system cleanup processes can remove orphaned staging files if the extension terminates unexpectedly.
Why does the tool use SHA-256 checksums rather than simple timestamp or size comparisons?
SHA-256 provides cryptographic assurance that the content written to the staging directory matches exactly what was computed during the transaction building phase. This protects against subtle corruption from cosmic rays, disk faults, or memory errors that simpler metadata checks might miss, ensuring that only verified content replaces original files.
Can users preview renames without the risk of accidental application?
Yes. The preview phase runs with the --dry-run flag and operates entirely in memory without filesystem writes. The apply phase requires a separate explicit action—clicking the Apply button in the sandboxed webview—which sends a distinct message type to the extension host. This architectural separation ensures that viewing a preview can never trigger accidental modifications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →