1111# -i, --independent Don't install dependencies automatically
1212# -k, --no-cache Don't use the download cache
1313# -s, --skip-hash-check Skip hash validation (use with caution!)
14+ # -w, --virustotal-check Check the download against VirusTotal (may be slow)
1415# -q, --quiet Hide extraneous messages
1516# -a, --all Update all apps (alternative to '*')
1617
@@ -29,11 +30,12 @@ if (get_config USE_SQLITE_CACHE) {
2930 . " $PSScriptRoot \..\lib\database.ps1"
3031}
3132
32- $opt , $apps , $err = getopt $args ' gfiksqa ' ' global' , ' force' , ' independent' , ' no-cache' , ' skip-hash-check' , ' quiet' , ' all'
33+ $opt , $apps , $err = getopt $args ' gfikswqa ' ' global' , ' force' , ' independent' , ' no-cache' , ' skip-hash-check ' , ' virustotal -check' , ' quiet' , ' all'
3334if ($err ) { error " scoop update: $err " ; exit 1 }
3435$global = $opt.g -or $opt.global
3536$force = $opt.f -or $opt.force
3637$check_hash = ! ($opt.s -or $opt .' skip-hash-check' )
38+ $check_virustotal = $opt.w -or $opt .' virustotal-check' -or (get_config USE_VIRUSTOTAL $false )
3739$use_cache = ! ($opt.k -or $opt .' no-cache' )
3840$quiet = $opt.q -or $opt.quiet
3941$independent = $opt.i -or $opt.independent
@@ -258,7 +260,7 @@ function Sync-Bucket {
258260 }
259261}
260262
261- function update ($app , $global , $quiet = $false , $independent , $suggested , $use_cache = $true , $check_hash = $true ) {
263+ function update ($app , $global , $quiet = $false , $independent , $suggested , $use_cache = $true , $check_hash = $true , $check_virustotal = $false ) {
262264 $old_version = Select-CurrentVersion - AppName $app - Global:$global
263265 $old_manifest = installed_manifest $app $old_version $global
264266 $install = install_info $app $old_version $global
@@ -300,6 +302,38 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
300302 }
301303 # endregion Workaround for #2952
302304
305+ # can be multiple urls: if there are, then installer should go first to make 'installer.args' section work
306+ $urls = @ (script:url $manifest $architecture )
307+
308+ # Pre-check all URLs with VirusTotal before downloading
309+ $api_key = Get-VirusTotalApiKey
310+
311+ $safe_urls = @ ()
312+ if ($check_virustotal ) {
313+ foreach ($url in $urls ) {
314+ $hash = hash_for_url $manifest $url $architecture
315+ $reports = Check- VirusTotalUrl $app $url $hash $api_key $false
316+ $reports | ForEach-Object {
317+ $file_report = $_
318+ $url = $file_report .' App.Url'
319+
320+ $maliciousResults = $file_report .' FileReport.Malicious'
321+ $suspiciousResults = $file_report .' FileReport.Suspicious'
322+ if ($maliciousResults -gt 0 -or $suspiciousResults -gt 0 ) {
323+ warn " $app `: One or more VirusTotal checks failed. Aborting before download."
324+ } else {
325+ info " $app `: Safe URL: $url "
326+ $safe_urls += $url
327+ }
328+ }
329+ }
330+ if ($safe_urls.Count -eq 0 ) {
331+ abort " No URL passed VirusTotal check for $app . Aborting before download."
332+ }
333+ } else {
334+ $safe_urls = $urls
335+ }
336+
303337 # region Workaround
304338 # Workaround for https://github.com/ScoopInstaller/Scoop/issues/2220 until install is refactored
305339 # Remove and replace whole region after proper fix
@@ -309,7 +343,7 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
309343 } else {
310344 $urls = script:url $manifest $architecture
311345
312- foreach ($url in $urls ) {
346+ foreach ($url in $safe_urls ) {
313347 Invoke-CachedDownload $app $version $url $null $manifest.cookie $true
314348
315349 if ($check_hash ) {
@@ -376,12 +410,12 @@ function update($app, $global, $quiet = $false, $independent, $suggested, $use_c
376410 }
377411
378412 if ($independent ) {
379- install_app $app $architecture $global $suggested $use_cache $check_hash
413+ install_app $app $architecture $global $suggested $use_cache $check_hash $check_virustotal
380414 } else {
381415 # Also add missing dependencies
382416 $apps = @ (Get-Dependency $app $architecture ) -ne $app
383417 ensure_none_failed $apps
384- $apps.Where ({ ! (installed $_ ) }) + $app | ForEach-Object { install_app $_ $architecture $global $suggested $use_cache $check_hash }
418+ $apps.Where ({ ! (installed $_ ) }) + $app | ForEach-Object { install_app $_ $architecture $global $suggested $use_cache $check_hash $check_virustotal }
385419 }
386420}
387421
@@ -462,7 +496,7 @@ if (-not ($apps -or $all)) {
462496
463497 $suggested = @ {}
464498 # $outdated is a list of ($app, $global) tuples
465- $outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache $check_hash }
499+ $outdated | ForEach-Object { update @_ $quiet $independent $suggested $use_cache $check_hash $check_virustotal }
466500}
467501
468502exit 0
0 commit comments