@@ -217,7 +217,19 @@ cli() ->
217
217
> .github/scripts/otp-compliance.es sbom vendor --sbom-file otp.spdx.json
218
218
""" ,
219
219
arguments => [ sbom_option ()],
220
- handler => fun sbom_vendor /1 }
220
+ handler => fun sbom_vendor /1 },
221
+
222
+ " osv-scan" =>
223
+ #{ help =>
224
+ """
225
+ Performs vulnerability scanning on vendor libraries
226
+
227
+ Example:
228
+
229
+ > .github/scripts/otp-compliance.es sbom osv-scan
230
+ """ ,
231
+ arguments => [ versions_file ()],
232
+ handler => fun osv_scan /1 }
221
233
}},
222
234
" explore" =>
223
235
#{ help => """
@@ -317,6 +329,11 @@ sbom_option() ->
317
329
default => " bom.spdx.json" ,
318
330
long => " -sbom-file" }.
319
331
332
+ versions_file () ->
333
+ #{name => version ,
334
+ type => binary ,
335
+ long => " -version" }.
336
+
320
337
ntia_checker () ->
321
338
#{name => ntia_checker ,
322
339
type => boolean ,
@@ -1207,6 +1224,216 @@ generate_vendor_purl(Package) ->
1207
1224
[create_externalRef_purl (Description , <<Purl /binary , " @" , Vsn /binary >>)]
1208
1225
end .
1209
1226
1227
+ osv_scan (#{version := Version }) ->
1228
+ application :ensure_all_started ([ssl , inets ]),
1229
+ OSVQuery = vendor_by_version (Version ),
1230
+
1231
+ io :format (" [OSV] Information sent~n~s~n " , [json :format (OSVQuery )]),
1232
+
1233
+ OSV = json :encode (OSVQuery ),
1234
+
1235
+ Format = " application/x-www-form-urlencoded" ,
1236
+ URI = " https://api.osv.dev/v1/querybatch" ,
1237
+ Content = {URI , [], Format , OSV },
1238
+ Result = httpc :request (post , Content , [], []),
1239
+ Vulns =
1240
+ case Result of
1241
+ {ok ,{{_ , 200 ,_ }, _Headers , Body }} ->
1242
+ #{~ " results" := OSVResults } = json :decode (erlang :list_to_binary (Body )),
1243
+ Vulnerabilities = lists :filter (fun (#{~ " vulns" := _Ids }) -> true ; (_ ) -> false end , OSVResults ),
1244
+ case Vulnerabilities of
1245
+ [] ->
1246
+ [];
1247
+ _ ->
1248
+ NameVulnerabilities = lists :zip (osv_names (OSVQuery ), OSVResults ),
1249
+ lists :filtermap (fun ({Name , #{~ " vulns" := Ids }}) ->
1250
+ {true , {Name , [Id || #{~ " id" := Id } <- Ids ]}};
1251
+ (_ ) ->
1252
+ false
1253
+ end , NameVulnerabilities )
1254
+ end ;
1255
+ {error , Error } ->
1256
+ {error , [URI , Error ]}
1257
+ end ,
1258
+ Vulns1 = ignore_vex_cves (Vulns ),
1259
+ FormattedVulns = format_vulnerabilities (Vulns1 ),
1260
+ report_vulnerabilities (FormattedVulns ).
1261
+
1262
+ % % TODO: fix by reading VEX files from erlang/vex or repo containing VEX files
1263
+ ignore_vex_cves (Vulns ) ->
1264
+ lists :foldl (fun ({~ " github.com/wxWidgets/wxWidgets" , _CVEs }, Acc ) ->
1265
+ % % OTP cannot be vulnerable to wxwidgets because
1266
+ % % we only take documentation.
1267
+ Acc ;
1268
+ ({Name , CVEs }, Acc ) ->
1269
+ case maps :get (Name , non_vulnerable_cves (), not_found ) of
1270
+ not_found ->
1271
+ [{Name , CVEs } | Acc ];
1272
+ NonCVEs ->
1273
+ case CVEs -- NonCVEs of
1274
+ [] ->
1275
+ Acc ;
1276
+ Vs ->
1277
+ [{Name , Vs } | Acc ]
1278
+ end
1279
+ end
1280
+ end , [], Vulns ).
1281
+
1282
+ non_vulnerable_cves () ->
1283
+ #{ ~ " github.com/madler/zlib" => [~ " CVE-2023-45853" ],
1284
+ ~ " github.com/openssl/openssl" =>
1285
+ [~ " CVE-2024-12797" , ~ " CVE-2023-6129" , ~ " CVE-2023-6237" , ~ " CVE-2024-0727" ,
1286
+ ~ " CVE-2024-13176" , ~ " CVE-2024-2511" , ~ " CVE-2024-4603" , ~ " CVE-2024-4741" ,
1287
+ ~ " CVE-2024-5535" , ~ " CVE-2024-6119" , ~ " CVE-2024-9143" ],
1288
+ ~ " github.com/PCRE2Project/pcre2" => [~ " OSV-2025-300" ]}.
1289
+
1290
+
1291
+ format_vulnerabilities ({error , ErrorContext }) ->
1292
+ {error , ErrorContext };
1293
+ format_vulnerabilities (ExistingVulnerabilities ) when is_list (ExistingVulnerabilities ) ->
1294
+ lists :map (fun ({N , Ids }) ->
1295
+ io_lib :format (" - ~s : ~s~n " , [N , lists :join (" ," , Ids )])
1296
+ end , ExistingVulnerabilities ).
1297
+
1298
+ report_vulnerabilities ([]) ->
1299
+ io :format (" [OSV] No vulnerabilities found.~n " );
1300
+ report_vulnerabilities ({error , [URI , Error ]}) ->
1301
+ fail (" [OSV] POST request to ~p errors: ~p " , [URI , Error ]);
1302
+ report_vulnerabilities (FormatVulns ) ->
1303
+ fail (" [OSV] There are existing vulnerabilities:~n~s " , [FormatVulns ]).
1304
+
1305
+ osv_names (#{~ " queries" := Packages }) ->
1306
+ lists :map (fun osv_names /1 , Packages );
1307
+ osv_names (#{~ " package" := #{~ " name" := Name }}) ->
1308
+ Name .
1309
+
1310
+ generate_osv_query (Packages ) ->
1311
+ #{~ " queries" => lists :foldl (fun generate_osv_query /2 , [], Packages )}.
1312
+ generate_osv_query (#{~ " versionInfo" := Vsn , ~ " ecosystem" := Ecosystem , ~ " name" := Name }, Acc ) ->
1313
+ Package = #{~ " package" => #{~ " name" => Name , ~ " ecosystem" => Ecosystem }, ~ " version" => Vsn },
1314
+ [Package | Acc ];
1315
+ generate_osv_query (#{~ " sha" := SHA , ~ " downloadLocation" := Location }, Acc ) ->
1316
+ case string :prefix (Location , ~ " https://" ) of
1317
+ nomatch ->
1318
+ Acc ;
1319
+ URI ->
1320
+ Package = #{~ " package" => #{~ " name" => URI }, ~ " commit" => SHA },
1321
+ [Package | Acc ]
1322
+ end ;
1323
+ generate_osv_query (_ , Acc ) ->
1324
+ Acc .
1325
+
1326
+ vendor_by_version (~ " maint-25" ) ->
1327
+ [#{~ " package" =>
1328
+ #{~ " commit" => ~ " 21767c654d31d2dccdde4330529775c6c5fd5389" ,
1329
+ ~ " name" => ~ " github.com/madler/zlib" }},
1330
+ #{~ " package" =>
1331
+ #{~ " commit" => ~ " 23ddf56b00f47d8aa0c82ad225e4b3a92661da7e" ,
1332
+ ~ " name" => ~ " github.com/asmjit/asmjit" }},
1333
+ #{~ " package" =>
1334
+ #{ ~ " commit" => ~ " e745bad3b1d05b5b19ec652d68abb37865ffa454" ,
1335
+ ~ " name" => ~ " github.com/microsoft/STL" }},
1336
+ #{~ " package" =>
1337
+ #{~ " commit" => ~ " 844864ac213bdbf1fb57e6f51c653b3d90af0937" ,
1338
+ ~ " name" => ~ " github.com/ulfjack/ryu" }},
1339
+ #{~ " package" =>
1340
+ #{ ~ " commit" => ~ " 01d5e2318405362b4de5e670c90d9b40a351d053" ,
1341
+ ~ " name" => ~ " github.com/openssl/openssl"
1342
+ }},
1343
+ #{~ " package" => % 8.45, not offial but the official sourceforge is not available
1344
+ #{~ " commit" => ~ " 3934406b50b8c2a4e2fc7362ed8026224ac90828" ,
1345
+ ~ " name" => ~ " github.com/nektro/pcre-8.45" }},
1346
+ #{~ " package" => % 3.1.4
1347
+ #{ ~ " commit" => ~ " 01d5e2318405362b4de5e670c90d9b40a351d053" ,
1348
+ ~ " name" => ~ " github.com/openssl/openssl" }},
1349
+ #{~ " package" =>
1350
+ #{~ " ecosystem" => ~ " npm" ,
1351
+ ~ " name" => ~ " tablesorter" ,
1352
+ ~ " version" => ~ " 2.32" }},
1353
+ #{~ " package" =>
1354
+ #{~ " ecosystem" => ~ " npm" ,
1355
+ ~ " name" => ~ " jquery" ,
1356
+ ~ " version" => ~ " 3.7.1" }},
1357
+ #{~ " package" => % ok
1358
+ #{~ " commit" => ~ " dc585039bbd426829e3433002023a93f9bedd0c2" ,
1359
+ ~ " name" => ~ " github.com/wxWidgets/wxWidgets" }}
1360
+ ];
1361
+ vendor_by_version (~ " maint-26" ) ->
1362
+ [#{~ " package" => % % v1.2.13
1363
+ #{~ " commit" => ~ " 04f42ceca40f73e2978b50e93806c2a18c1281fc" ,
1364
+ ~ " name" => ~ " github.com/madler/zlib"
1365
+ }},
1366
+ #{~ " package" =>
1367
+ #{~ " commit" => ~ " 915186f6c5c2f5a4638e5cb97ccc23d741521a64" ,
1368
+ ~ " name" => ~ " github.com/asmjit/asmjit"
1369
+ }},
1370
+ #{~ " package" =>
1371
+ #{~ " commit" => ~ " e745bad3b1d05b5b19ec652d68abb37865ffa454" ,
1372
+ ~ " name" => ~ " github.com/microsoft/STL" }},
1373
+ #{~ " package" =>
1374
+ #{~ " commit" => ~ " 844864ac213bdbf1fb57e6f51c653b3d90af0937" ,
1375
+ ~ " name" => ~ " github.com/ulfjack/ryu" }},
1376
+ #{~ " package" => % 3.1.4
1377
+ #{~ " commit" => ~ " 01d5e2318405362b4de5e670c90d9b40a351d053" ,
1378
+ ~ " name" => ~ " github.com/openssl/openssl" }},
1379
+ #{~ " package" => % 8.45, not offial but the official sourceforge is not available
1380
+ #{~ " commit" => ~ " 3934406b50b8c2a4e2fc7362ed8026224ac90828" ,
1381
+ ~ " name" => ~ " github.com/nektro/pcre-8.45" }},
1382
+ #{~ " package" => % 3.1.4
1383
+ #{~ " commit" => ~ " 01d5e2318405362b4de5e670c90d9b40a351d053" ,
1384
+ ~ " name" => ~ " github.com/openssl/openssl" }},
1385
+ #{~ " package" =>
1386
+ #{~ " ecosystem" => ~ " npm" ,
1387
+ ~ " name" => ~ " tablesorter" ,
1388
+ ~ " version" => ~ " 2.32" }},
1389
+ #{~ " package" =>
1390
+ #{~ " ecosystem" => ~ " npm" ,
1391
+ ~ " name" => ~ " jquery" ,
1392
+ ~ " version" => ~ " 3.7.1" }},
1393
+ #{~ " package" =>
1394
+ #{~ " commit" => ~ " dc585039bbd426829e3433002023a93f9bedd0c2" ,
1395
+ ~ " name" => ~ " github.com/wxWidgets/wxWidgets" }}
1396
+ ];
1397
+ vendor_by_version (~ " maint-27" ) ->
1398
+ [#{~ " package" => % % v1.2.13
1399
+ #{~ " commit" => ~ " 04f42ceca40f73e2978b50e93806c2a18c1281fc" ,
1400
+ ~ " name" => ~ " github.com/madler/zlib" }},
1401
+ #{~ " package" =>
1402
+ #{~ " commit" => ~ " a465fe71ab3d0e224b2b4bd0fac69ae68ab9239d" ,
1403
+ ~ " name" => ~ " github.com/asmjit/asmjit"
1404
+ }},
1405
+ #{~ " package" =>
1406
+ #{~ " commit" => ~ " e745bad3b1d05b5b19ec652d68abb37865ffa454" ,
1407
+ ~ " name" => ~ " github.com/microsoft/STL" }},
1408
+ #{~ " package" =>
1409
+ #{~ " commit" => ~ " 844864ac213bdbf1fb57e6f51c653b3d90af0937" ,
1410
+ ~ " name" => ~ " github.com/ulfjack/ryu" }},
1411
+ #{~ " package" => % 3.1.4
1412
+ #{~ " commit" => ~ " 01d5e2318405362b4de5e670c90d9b40a351d053" ,
1413
+ ~ " name" => ~ " github.com/openssl/openssl" }},
1414
+ #{~ " package" => % 8.45, not offial but the official sourceforge is not available
1415
+ #{~ " commit" => ~ " 3934406b50b8c2a4e2fc7362ed8026224ac90828" ,
1416
+ ~ " name" => ~ " github.com/nektro/pcre-8.45" }},
1417
+ #{~ " package" => % 3.1.4
1418
+ #{~ " commit" => ~ " 01d5e2318405362b4de5e670c90d9b40a351d053" ,
1419
+ ~ " name" => ~ " github.com/openssl/openssl" }},
1420
+ #{~ " package" =>
1421
+ #{~ " ecosystem" => ~ " npm" ,
1422
+ ~ " name" => ~ " tablesorter" ,
1423
+ ~ " version" => ~ " 2.32" }},
1424
+ #{~ " package" =>
1425
+ #{~ " ecosystem" => ~ " npm" ,
1426
+ ~ " name" => ~ " jquery" ,
1427
+ ~ " version" => ~ " 3.7.1" }},
1428
+ #{~ " package" =>
1429
+ #{~ " commit" => ~ " dc585039bbd426829e3433002023a93f9bedd0c2" ,
1430
+ ~ " name" => ~ " github.com/wxWidgets/wxWidgets" }}
1431
+ ];
1432
+ vendor_by_version (_ ) ->
1433
+ VendorSrcFiles = find_vendor_src_files (" ." ),
1434
+ Packages = generate_vendor_info_package (VendorSrcFiles ),
1435
+ generate_osv_query (Packages ).
1436
+
1210
1437
cleanup_path (<<" ./" , Path /binary >>) when is_binary (Path ) -> Path ;
1211
1438
cleanup_path (Path ) when is_binary (Path ) -> Path .
1212
1439
@@ -1559,7 +1786,7 @@ root_vendor_packages() ->
1559
1786
minimum_vendor_packages () ->
1560
1787
% % self-contained
1561
1788
root_vendor_packages () ++
1562
- [~ " tcl" , ~ " ryu_to_chars" , ~ " json-test-suite" , ~ " openssl" , ~ " Autoconf" , ~ " wx" , ~ " jquery" , ~ " jquery- tablesorter" ].
1789
+ [~ " tcl" , ~ " ryu_to_chars" , ~ " json-test-suite" , ~ " openssl" , ~ " Autoconf" , ~ " wx" , ~ " jquery" , ~ " tablesorter" ].
1563
1790
1564
1791
test_copyright_not_empty (#{~ " packages" := Packages }) ->
1565
1792
true = lists :all (fun (#{~ " copyrightText" := Copyright }) -> Copyright =/= ~ " " end , Packages ),
0 commit comments