@@ -720,5 +720,67 @@ def test_malformed_record_fails_without_writing(self):
720720 self .assertFalse (os .path .exists (csaf ))
721721
722722
723+ class TestPathIdValidation (unittest .TestCase ):
724+ """cveId and --advisory-id are interpolated into output filenames; a
725+ record is fetched from a remote API and parsed as arbitrary JSON, so an
726+ unvalidated id is an arbitrary-file-write vector. Guard both."""
727+
728+ def _run (self , args ):
729+ return subprocess .run ([sys .executable , str (SCRIPT )] + args ,
730+ capture_output = True , text = True )
731+
732+ @staticmethod
733+ def _record (cve_id ):
734+ return {'cveMetadata' : {'cveId' : cve_id },
735+ 'containers' : {'cna' : {
736+ 'descriptions' : [{'lang' : 'en' , 'value' : 'test desc' }],
737+ 'affected' : [{'vendor' : 'wolfSSL' , 'product' : 'wolfSSL' ,
738+ 'versions' : []}]}}}
739+
740+ def test_traversal_cveid_rejected_and_writes_nothing_outside (self ):
741+ with tempfile .TemporaryDirectory () as d :
742+ rec = os .path .join (d , 'evil.json' )
743+ with open (rec , 'w' ) as f :
744+ json .dump (self ._record ('../ESCAPED' ), f )
745+ out = os .path .join (d , 'out' , 'batch' )
746+ os .makedirs (out )
747+ r = self ._run (['--cve-record' , rec , '--out-dir' , out ])
748+ self .assertNotEqual (r .returncode , 0 , r .stdout )
749+ self .assertIn ('unsafe cveId' , r .stderr )
750+ # The escaped path (sibling of out/, i.e. d/out/ESCAPED.*) must
751+ # not have been written.
752+ escaped = os .path .join (d , 'out' , 'ESCAPED.csaf.json' )
753+ self .assertFalse (os .path .exists (escaped ), escaped )
754+
755+ def test_absolute_cveid_rejected (self ):
756+ with tempfile .TemporaryDirectory () as d :
757+ rec = os .path .join (d , 'abs.json' )
758+ with open (rec , 'w' ) as f :
759+ json .dump (self ._record ('/etc/pwned' ), f )
760+ r = self ._run (['--cve-record' , rec , '--out-dir' , d ])
761+ self .assertNotEqual (r .returncode , 0 , r .stdout )
762+ self .assertIn ('unsafe cveId' , r .stderr )
763+
764+ def test_well_formed_cveid_accepted (self ):
765+ with tempfile .TemporaryDirectory () as d :
766+ rec = os .path .join (d , 'good.json' )
767+ with open (rec , 'w' ) as f :
768+ json .dump (self ._record ('CVE-2026-12345' ), f )
769+ r = self ._run (['--cve-record' , rec , '--out-dir' , d ])
770+ self .assertEqual (r .returncode , 0 , r .stderr )
771+ self .assertTrue (
772+ os .path .exists (os .path .join (d , 'CVE-2026-12345.csaf.json' )))
773+
774+ def test_traversal_advisory_id_rejected (self ):
775+ with tempfile .TemporaryDirectory () as d :
776+ rec = os .path .join (d , 'good.json' )
777+ with open (rec , 'w' ) as f :
778+ json .dump (self ._record ('CVE-2026-12345' ), f )
779+ r = self ._run (['--cve-record' , rec , '--out-dir' , d ,
780+ '--advisory-id' , '../evil' ])
781+ self .assertNotEqual (r .returncode , 0 , r .stdout )
782+ self .assertIn ('unsafe --advisory-id' , r .stderr )
783+
784+
723785if __name__ == '__main__' :
724786 unittest .main ()
0 commit comments