@@ -47,7 +47,10 @@ describe("getCacheDir", () => {
4747 const originalPlatform = Object . getOwnPropertyDescriptor ( process , "platform" ) ;
4848 const originalLocalAppData = process . env . LOCALAPPDATA ;
4949 try {
50- Object . defineProperty ( process , "platform" , { value : "win32" , configurable : true } ) ;
50+ Object . defineProperty ( process , "platform" , {
51+ value : "win32" ,
52+ configurable : true ,
53+ } ) ;
5154 process . env . LOCALAPPDATA = "C:\\Users\\user\\AppData\\Local" ;
5255 const dir = getCacheDir ( ) ;
5356 // path.join uses the host OS separator in tests (macOS: /), so we just
@@ -67,7 +70,10 @@ describe("getCacheDir", () => {
6770 const originalPlatform = Object . getOwnPropertyDescriptor ( process , "platform" ) ;
6871 const originalLocalAppData = process . env . LOCALAPPDATA ;
6972 try {
70- Object . defineProperty ( process , "platform" , { value : "win32" , configurable : true } ) ;
73+ Object . defineProperty ( process , "platform" , {
74+ value : "win32" ,
75+ configurable : true ,
76+ } ) ;
7177 delete process . env . LOCALAPPDATA ;
7278 const dir = getCacheDir ( ) ;
7379 expect ( dir ) . toContain ( "AppData" ) ;
@@ -84,7 +90,10 @@ describe("getCacheDir", () => {
8490 const originalPlatform = Object . getOwnPropertyDescriptor ( process , "platform" ) ;
8591 const originalXdg = process . env . XDG_CACHE_HOME ;
8692 try {
87- Object . defineProperty ( process , "platform" , { value : "linux" , configurable : true } ) ;
93+ Object . defineProperty ( process , "platform" , {
94+ value : "linux" ,
95+ configurable : true ,
96+ } ) ;
8897 process . env . XDG_CACHE_HOME = "/custom/xdg/cache" ;
8998 const dir = getCacheDir ( ) ;
9099 expect ( dir ) . toContain ( "custom" ) ;
@@ -102,7 +111,10 @@ describe("getCacheDir", () => {
102111 const originalPlatform = Object . getOwnPropertyDescriptor ( process , "platform" ) ;
103112 const originalXdg = process . env . XDG_CACHE_HOME ;
104113 try {
105- Object . defineProperty ( process , "platform" , { value : "linux" , configurable : true } ) ;
114+ Object . defineProperty ( process , "platform" , {
115+ value : "linux" ,
116+ configurable : true ,
117+ } ) ;
106118 delete process . env . XDG_CACHE_HOME ;
107119 const dir = getCacheDir ( ) ;
108120 expect ( dir ) . toContain ( ".cache" ) ;
@@ -117,7 +129,10 @@ describe("getCacheDir", () => {
117129 delete process . env . GITHUB_CODE_SEARCH_CACHE_DIR ;
118130 const originalPlatform = Object . getOwnPropertyDescriptor ( process , "platform" ) ;
119131 try {
120- Object . defineProperty ( process , "platform" , { value : "freebsd" , configurable : true } ) ;
132+ Object . defineProperty ( process , "platform" , {
133+ value : "freebsd" ,
134+ configurable : true ,
135+ } ) ;
121136 const dir = getCacheDir ( ) ;
122137 expect ( dir ) . toContain ( ".github-code-search" ) ;
123138 expect ( dir ) . toContain ( "cache" ) ;
@@ -224,3 +239,56 @@ describe("writeCache / readCache round-trip", () => {
224239 expect ( ( ) => writeCache ( "key.json" , { data : 1 } ) ) . not . toThrow ( ) ;
225240 } ) ;
226241} ) ;
242+
243+ // ─── Path Traversal Security Tests ────────────────────────────────────────────
244+
245+ describe ( "Path traversal vulnerability mitigation" , ( ) => {
246+ it ( "rejects readCache with relative parent directory traversal (../)" , ( ) => {
247+ // Attempt to read outside the cache directory using ../
248+ const maliciousKey = "../../../etc/passwd" ;
249+ const result = readCache ( maliciousKey ) ;
250+ expect ( result ) . toBeNull ( ) ;
251+ } ) ;
252+
253+ it ( "rejects writeCache with relative parent directory traversal (../)" , ( ) => {
254+ // Attempt to write outside the cache directory using ../
255+ const maliciousKey = "../../../tmp/malicious.json" ;
256+ const maliciousData = { exploit : "attempt" } ;
257+ // Should not throw, but should silently reject
258+ expect ( ( ) => writeCache ( maliciousKey , maliciousData ) ) . not . toThrow ( ) ;
259+ // Verify the file was not written to the malicious location
260+ expect ( readCache ( maliciousKey ) ) . toBeNull ( ) ;
261+ } ) ;
262+
263+ it ( "rejects readCache with absolute path" , ( ) => {
264+ // Attempt to read an absolute path outside the cache directory
265+ const maliciousKey = "/etc/passwd" ;
266+ const result = readCache ( maliciousKey ) ;
267+ expect ( result ) . toBeNull ( ) ;
268+ } ) ;
269+
270+ it ( "rejects writeCache with absolute path" , ( ) => {
271+ // Attempt to write to an absolute path outside the cache directory
272+ const maliciousKey = "/tmp/malicious-absolute.json" ;
273+ const maliciousData = { exploit : "absolute" } ;
274+ expect ( ( ) => writeCache ( maliciousKey , maliciousData ) ) . not . toThrow ( ) ;
275+ expect ( readCache ( maliciousKey ) ) . toBeNull ( ) ;
276+ } ) ;
277+
278+ it ( "rejects readCache with encoded path traversal (%2e%2e/)" , ( ) => {
279+ // URL-encoded path traversal attempt
280+ const maliciousKey = "%2e%2e/%2e%2e/etc/passwd" ;
281+ const result = readCache ( maliciousKey ) ;
282+ // The key is treated as a literal filename, not decoded, so it's safe
283+ // but should still not exist
284+ expect ( result ) . toBeNull ( ) ;
285+ } ) ;
286+
287+ it ( "allows reading legitimate cache files with safe names" , ( ) => {
288+ // Verify that normal operation still works
289+ const safeKey = "teams__myorg__squad-.json" ;
290+ const data = { teams : [ "squad-alpha" ] } ;
291+ writeCache ( safeKey , data ) ;
292+ expect ( readCache ( safeKey ) ) . toEqual ( data ) ;
293+ } ) ;
294+ } ) ;
0 commit comments