@@ -216,7 +216,18 @@ cli() ->
216216 > .github/scripts/otp-compliance.es sbom vendor --sbom-file otp.spdx.json
217217 """ ,
218218 arguments => [ sbom_option ()],
219- handler => fun sbom_vendor /1 }
219+ handler => fun sbom_vendor /1 },
220+
221+ " osv-scan" =>
222+ #{ help =>
223+ """
224+ Performs vulnerability scanning on vendor libraries
225+
226+ Example:
227+
228+ > .github/scripts/otp-compliance.es sbom osv-scan
229+ """ ,
230+ handler => fun osv_scan /1 }
220231 }},
221232 " explore" =>
222233 #{ help => """
@@ -1205,6 +1216,68 @@ generate_vendor_purl(Package) ->
12051216 [create_externalRef_purl (Description , <<Purl /binary , " @" , Vsn /binary >>)]
12061217 end .
12071218
1219+ osv_scan (_ ) ->
1220+ application :ensure_all_started ([ssl , inets ]),
1221+ URI = " https://api.osv.dev/v1/querybatch" ,
1222+ Format = " application/x-www-form-urlencoded" ,
1223+
1224+ VendorSrcFiles = find_vendor_src_files (" ." ),
1225+ Packages = generate_vendor_info_package (VendorSrcFiles ),
1226+
1227+ OSVQuery = generate_osv_query (Packages ),
1228+ io :format (" [OSV] Information sent~n~s~n " , [json :format (OSVQuery )]),
1229+ OSV = json :encode (OSVQuery ),
1230+
1231+ Content = {URI , [], Format , OSV },
1232+ Result = httpc :request (post , Content , [], []),
1233+ case Result of
1234+ {ok ,{{_ , 200 ,_ }, _Headers , Body }} ->
1235+ #{~ " results" := OSVResults } = json :decode (erlang :list_to_binary (Body )),
1236+ Vulnerabilities = lists :filter (fun (#{~ " vulns" := _Ids }) -> true ; (_ ) -> false end , OSVResults ),
1237+ case Vulnerabilities of
1238+ [] ->
1239+ io :format (" [OSV] No vulnerabilities found.~n " );
1240+ _ ->
1241+ FormatVulns = format_vulnerabilities (OSVQuery , OSVResults ),
1242+ fail (" [OSV] There are existing vulnerabilities:~n~s " , [FormatVulns ])
1243+ end ;
1244+ {error , Error } ->
1245+ fail (" [OSV] POST request to ~p errors: ~p " , [URI , Error ])
1246+ end .
1247+
1248+ format_vulnerabilities (OSVQuery , OSVResults ) ->
1249+ NameVulnerabilities = lists :zip (osv_names (OSVQuery ), OSVResults ),
1250+ ExistingVulnerabilities = lists :filtermap (fun ({Name , #{~ " vulns" := Ids }}) ->
1251+ {true , {Name , [Id || #{~ " id" := Id } <- Ids ]}};
1252+ (_ ) ->
1253+ false
1254+ end , NameVulnerabilities ),
1255+ lists :map (fun ({N , Ids }) ->
1256+ io_lib :format (" - ~s : ~s~n " , [N , lists :join (" ," , Ids )])
1257+ end , ExistingVulnerabilities ).
1258+
1259+ osv_names (#{~ " queries" := Packages }) ->
1260+ lists :map (fun osv_names /1 , Packages );
1261+ osv_names (#{~ " package" := #{~ " name" := Name }}) ->
1262+ Name .
1263+
1264+ generate_osv_query (Packages ) ->
1265+ #{~ " queries" => lists :foldl (fun generate_osv_query /2 , [], Packages )}.
1266+ generate_osv_query (#{~ " versionInfo" := Vsn , ~ " ecosystem" := Ecosystem , ~ " name" := Name }, Acc ) ->
1267+ Package = #{~ " package" => #{~ " name" => Name , ~ " ecosystem" => Ecosystem }, ~ " version" => Vsn },
1268+ [Package | Acc ];
1269+ generate_osv_query (#{~ " sha" := SHA , ~ " downloadLocation" := Location }, Acc ) ->
1270+ case string :prefix (Location , ~ " https://" ) of
1271+ nomatch ->
1272+ Acc ;
1273+ URI ->
1274+ Package = #{~ " package" => #{~ " name" => URI }, ~ " commit" => SHA },
1275+ [Package | Acc ]
1276+ end ;
1277+ generate_osv_query (_ , Acc ) ->
1278+ Acc .
1279+
1280+
12081281cleanup_path (<<" ./" , Path /binary >>) when is_binary (Path ) -> Path ;
12091282cleanup_path (Path ) when is_binary (Path ) -> Path .
12101283
0 commit comments