MCPcopy Create free account
hub / github.com/SethGammon/Citadel / run

Function run

hooks_src/protect-files.js:92–214  ·  view source on GitHub ↗
(input)

Source from the content-addressed store, hash-verified

90}
91
92function run(input) {
93 let event;
94 try {
95 event = JSON.parse(input);
96 } catch {
97 health.logBlock('protect-files', 'parse-fail', 'Could not parse stdin JSON');
98 // Fail closed on parse failure — cannot determine if action is safe
99 blockOutput('protect-files', 'error', '[protect-files] Could not parse hook input — blocking as safety measure.');
100 process.exit(2);
101 }
102
103 const toolName = event.tool_name || '';
104 if (toolName !== 'Edit' && toolName !== 'Write' && toolName !== 'Read') {
105 process.exit(0);
106 }
107
108 const filePath = event.tool_input?.file_path || event.tool_input?.path || '';
109 if (!filePath) {
110 process.exit(0);
111 }
112
113 // Security: validate path for traversal and shell injection
114 const validation = health.validatePath(filePath);
115 if (!validation.safe) {
116 health.logBlock('protect-files', 'blocked', `${toolName} ${filePath} (${validation.violation})`);
117 blockOutput('protect-files', 'blocked',
118 `[protect-files] Blocked: ${validation.violation}`,
119 { file: filePath, tool: toolName, violation: validation.violation }
120 );
121 process.exit(2);
122 }
123
124 // Security: block writes to absolute paths outside project root. Reads may
125 // inspect neighboring docs, vaults, and reference repos; .env reads are still
126 // blocked by basename below.
127 const normalizedPath = path.normalize(health.canonicalizePath(filePath));
128 const normalizedRoot = path.normalize(health.canonicalizePath(PROJECT_ROOT));
129 const rootRelativePath = path.relative(normalizedRoot, normalizedPath);
130 const insideProject = rootRelativePath === '' || (
131 rootRelativePath !== '..'
132 && !rootRelativePath.startsWith(`..${path.sep}`)
133 && !path.isAbsolute(rootRelativePath)
134 );
135
136 // Claude Code native auto-memory lives outside the project root by design.
137 // Allow those writes before the outside-project-root block below.
138 if (toolName !== 'Read' && !insideProject && isNativeMemoryPath(normalizedPath)) {
139 process.exit(0);
140 }
141
142 if (toolName !== 'Read' && !insideProject) {
143 health.logBlock('protect-files', 'blocked', `${toolName} ${filePath} (outside project root)`);
144 blockOutput('protect-files', 'blocked',
145 `[protect-files] Blocked: ${filePath} is outside project root (${PROJECT_ROOT})`,
146 { file: filePath, tool: toolName, projectRoot: PROJECT_ROOT }
147 );
148 process.exit(2);
149 }

Callers 1

mainFunction · 0.70

Calls 5

blockOutputFunction · 0.85
isNativeMemoryPathFunction · 0.85
matchPatternFunction · 0.85
checkCampaignScopeFunction · 0.85
hookOutputFunction · 0.70

Tested by

no test coverage detected