@@ -240,4 +240,72 @@ describe('AllowedPathChecker', () => {
240240 const result = await checker . check ( input ) ;
241241 expect ( result . decision ) . toBe ( SafetyCheckDecision . ALLOW ) ;
242242 } ) ;
243+
244+ describe ( 'Security Regression: Case-Insensitive Blocklist & .vscode HITL' , ( ) => {
245+ it ( 'should deny sensitive paths like .git, .env, and node_modules case-insensitively, including Windows trailing character and NTFS ADS bypasses' , async ( ) => {
246+ const sensitivePaths = [
247+ path . join ( mockCwd , '.git' , 'config' ) ,
248+ path . join ( mockCwd , '.GIT' , 'config' ) ,
249+ path . join ( mockCwd , '.Git' , 'config' ) ,
250+ path . join ( mockCwd , '.env' ) ,
251+ path . join ( mockCwd , '.Env' ) ,
252+ path . join ( mockCwd , '.ENV' ) ,
253+ path . join ( mockCwd , 'node_modules' , 'package' , 'index.js' ) ,
254+ path . join ( mockCwd , 'NODE_MODULES' , 'package' , 'index.js' ) ,
255+ // Windows trailing character bypasses
256+ path . join ( mockCwd , '.git ' , 'config' ) ,
257+ path . join ( mockCwd , '.git.' , 'config' ) ,
258+ path . join ( mockCwd , '.env ' , 'config' ) ,
259+ path . join ( mockCwd , '.env.' , 'config' ) ,
260+ path . join ( mockCwd , 'node_modules ' , 'package' , 'index.js' ) ,
261+ // NTFS Alternate Data Stream bypasses
262+ path . join ( mockCwd , '.git::$DATA' , 'config' ) ,
263+ path . join ( mockCwd , '.env::$DATA' ) ,
264+ path . join ( mockCwd , 'node_modules::$DATA' , 'package' , 'index.js' ) ,
265+ ] ;
266+
267+ for ( const p of sensitivePaths ) {
268+ const input = createInput ( { path : p } ) ;
269+ const result = await checker . check ( input ) ;
270+ expect ( result . decision ) . toBe ( SafetyCheckDecision . DENY ) ;
271+ expect ( result . reason ) . toContain ( 'Access to sensitive path' ) ;
272+ }
273+ } ) ;
274+
275+ it ( 'should require ASK_USER for .vscode configuration files inside workspace, but deny them if outside, including NTFS ADS bypasses' , async ( ) => {
276+ const vscodePaths = [
277+ path . join ( mockCwd , '.vscode' , 'settings.json' ) ,
278+ path . join ( mockCwd , '.vscode' , 'settings.JSON' ) ,
279+ path . join ( mockCwd , '.VSCODE' , 'settings.json' ) ,
280+ path . join ( mockCwd , '.vscode' , 'launch.json' ) ,
281+ // Windows trailing character bypasses
282+ path . join ( mockCwd , '.vscode ' , 'settings.json' ) ,
283+ path . join ( mockCwd , '.vscode.' , 'settings.json' ) ,
284+ // NTFS Alternate Data Stream bypasses
285+ path . join ( mockCwd , '.vscode::$DATA' , 'settings.json' ) ,
286+ ] ;
287+
288+ for ( const p of vscodePaths ) {
289+ const input = createInput ( { path : p } ) ;
290+ const result = await checker . check ( input ) ;
291+ expect ( result . decision ) . toBe ( SafetyCheckDecision . ASK_USER ) ;
292+ expect ( result . reason ) . toContain (
293+ 'Modifying .vscode configuration files requires explicit user confirmation' ,
294+ ) ;
295+ }
296+
297+ // Verify that paths outside the workspace containing .vscode are strictly denied
298+ const outsideVscodePaths = [
299+ path . join ( testRootDir , 'outside' , '.vscode' , 'settings.json' ) ,
300+ path . join ( testRootDir , 'outside' , '.VSCODE' , 'settings.json' ) ,
301+ ] ;
302+
303+ for ( const p of outsideVscodePaths ) {
304+ const input = createInput ( { path : p } ) ;
305+ const result = await checker . check ( input ) ;
306+ expect ( result . decision ) . toBe ( SafetyCheckDecision . DENY ) ;
307+ expect ( result . reason ) . toContain ( 'outside of the allowed workspace' ) ;
308+ }
309+ } ) ;
310+ } ) ;
243311} ) ;
0 commit comments