Archify CLI Safe Output Path Resolution: How It Prevents Path Traversal and Overwrites
Archify validates every output path by resolving it against the current working directory, enforcing HTML-only file extensions, and containing writes within the repository root using pathIsInside() checks.
The Archify CLI implements a multi-layered safety system to ensure generated artifacts are written only to permitted locations. According to the tt-a1i/archify source code, this protection is coordinated across three core modules: the CLI driver, the output-path renderer, and the guard management system.
How Output Paths Are Resolved and Validated
When a user invokes archify render, the raw output value from CLI arguments passes through a strict normalization and validation pipeline in archify/renderers/shared/output-path.mjs.
Step 1: Absolute Path Resolution
The renderer first converts any relative path to an absolute location using Node.js path.resolve():
// L282 in output-path.mjs
const outputPath = path.resolve(cwd, rawOutput);
This ensures consistent path handling regardless of how the user specified the target.
Step 2: File Type Enforcement
If the render request originates from a meta source (a .archify description file), the renderer mandates that the output filename end with .html:
// L289 in output-path.mjs
if (sourceType === 'meta' && !outputPath.endsWith('.html')) {
throw createDiagnostic('invalid_output_extension', { expected: '.html' });
}
This prevents accidental generation of unrendered or potentially dangerous file types.
Step 3: Repository Containment Check
The critical security boundary is enforced by pathIsInside():
// L289-L290 in output-path.mjs
if (!pathIsInside(cwd, outputPath)) {
throw createDiagnostic('unsafe_output_path', {
hint: 'choose a safe output path and retry'
});
}
Any path that would escape the working directory—such as ../../etc/passwd or /absolute/path—triggers an immediate rejection with a diagnostic containing a supported fix hint.
Duplicate Prevention and Conflict Resolution
Output-path clashes are detected by scanning pending writes against otherOutputPath entries:
// L298-L312 in output-path.mjs
for (const pending of otherOutputPath) {
if (isConflict(outputPath, pending.resolvedPath)) {
throw createDiagnostic('output_path_collision', {
existing: pending.sourceFile
});
}
}
This prevents one architecture diagram from silently overwriting another when multiple inputs target the same destination.
Concurrent Write Protection with Guard Maps
The archify/renderers/shared/cli.mjs module implements atomic claim tracking through an outputPathGuards map (L13-L68):
// Conceptual guard implementation
const outputPathGuards = new Map();
function claimOutputPath(resolvedPath, guardToken) {
if (outputPathGuards.has(resolvedPath)) {
throw createDiagnostic('concurrent_output_conflict');
}
outputPathGuards.set(resolvedPath, guardToken);
return () => outputPathGuards.delete(resolvedPath); // release function
}
This mechanism ensures race-free writes even when Archify is invoked concurrently by different processes or async operations.
CLI Error Handling and Safe Path Suggestions
Diagnostic errors surface to users through archify/bin/archify.mjs with specific line references for actionable guidance:
| Line | Context | Behavior |
|---|---|---|
| L462 | Initial render validation | Validates output path before processing |
| L658 | Batch render mode | Applies checks to each output in sequence |
| L817 | Watch mode restart | Re-validates paths on file change |
| L1059 | CLI top-level error handler | Formats diagnostics with fix suggestions |
When validation fails, the CLI automatically suggests a sandboxed location under repoRoot/.archify/output/ as an alternative.
Practical Examples
Safe default location
archify render diagram.archify
# → Resolves to <cwd>/.archify/output/diagram.html
Explicit safe relative path
archify render diagram.archify -o docs/arch/flow.html
# → Validates: <cwd>/docs/arch/flow.html inside repo ✓
# → Extension: .html ✓
Blocked unsafe absolute path
archify render diagram.archify -o /etc/passwd
# Error: output path is outside the repository – choose a safe output path and retry
Blocked path traversal attempt
archify render diagram.archify -o ../../../tmp/exploit.html
# Error: output path is outside the repository – choose a safe output path and retry
Summary
- Resolution: All paths are normalized to absolute form using
path.resolve(cwd, rawOutput)at L282 - Containment:
pathIsInside(cwd, outputPath)blocks directory traversal attacks at L289-L290 - Type safety: Meta sources require
.htmloutput extensions at L289 - Deduplication: Pending write scanning prevents collisions at L298-L312
- Concurrency:
outputPathGuardsmap incli.mjs(L13-L68) enables atomic claims - UX recovery: Diagnostics in
archify.mjs(L462, L658, L817, L1059) guide users to safe alternatives
Frequently Asked Questions
How does Archify prevent writing files outside the repository?
Archify uses the pathIsInside() utility to verify that the resolved absolute path remains within the current working directory. This check executes immediately after path normalization in archify/renderers/shared/output-path.mjs (L289-L290). Any path that resolves above the repository root—whether through ../ traversal or absolute paths—triggers a diagnostic with guidance to choose a safe output path.
What happens if two inputs would generate the same output file?
Before committing any write, the renderer iterates through otherOutputPath to detect conflicts (L298-L312). When a collision is found, Archify throws an output_path_collision diagnostic naming the existing source file. This prevents accidental overwrites during batch operations or when multiple .archify files target similar naming conventions.
Does Archify allow non-HTML output formats?
For meta sources (.archify description files), the renderer enforces .html extensions at L289. This restriction ensures rendered outputs are safe web-viewable artifacts. Direct API usage or other source types may have different constraints, but the CLI pathway requires HTML for meta-driven renders.
How are concurrent CLI invocations protected from race conditions?
The outputPathGuards Map in archify/renderers/shared/cli.mjs (L13-L68) tracks claimed paths across asynchronous operations. Each successful claim returns a release function; duplicate claims for the same resolved path trigger concurrent_output_conflict diagnostics. This design provides atomicity without file-system locking.
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 →