Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check the R advisory database #37

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Package: multiverse.internals
Title: Internal Infrastructure for R-multiverse
Description: R-multiverse requires this internal infrastructure package to
automate contribution reviews and populate universes.
Version: 0.2.12
Version: 0.2.13
License: MIT + file LICENSE
URL: https://github.com/r-multiverse/multiverse.internals
BugReports: https://github.com/r-multiverse/multiverse.internals/issues
Expand All @@ -28,15 +28,17 @@ Authors@R: c(
Depends:
R (>= 3.6)
Imports:
gert,
gh,
igraph,
jsonlite,
nanonext,
pkgsearch,
stats,
utils,
vctrs
vctrs,
yaml
Suggests:
gert,
testthat (>= 3.0.0)
Encoding: UTF-8
Language: en-US
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export(review_pull_requests)
export(staging_is_active)
export(try_message)
export(update_staging)
importFrom(gert,git_clone)
importFrom(gh,gh)
importFrom(igraph,V)
importFrom(igraph,graph)
Expand All @@ -32,7 +33,9 @@ importFrom(nanonext,ncurl)
importFrom(nanonext,parse_url)
importFrom(nanonext,status_code)
importFrom(pkgsearch,cran_package)
importFrom(stats,aggregate)
importFrom(utils,available.packages)
importFrom(utils,compareVersion)
importFrom(vctrs,vec_rbind)
importFrom(vctrs,vec_slice)
importFrom(yaml,read_yaml)
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# multiverse.internals 0.2.13

* Record issues for vulnerabilities in <https://github.com/RConsortium/r-advisory-database>.

# multiverse.internals 0.2.12

* Amend argument defaults in `propose_snapshot()` to include source files.
Expand Down
70 changes: 63 additions & 7 deletions R/issues_descriptions.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
#' @family issues
#' @description Report issues with the `DESCRIPTION` files of packages.
#' @details [issues_descriptions()] scans downloaded metadata from the
#' `PACKAGES.json` file of an R universe and reports issues with a
#' package's description file, such as the presence of a
#' `"Remotes"` field.
#' `PACKAGES.json` file of an R universe and scans for specific issues in a
#' package's description file:
#' 1. The presence of a `"Remotes"` field.
#' 2. There is a security advisory at
#' <https://github.com/RConsortium/r-advisory-database>
#' for the given package version.
#' @inheritSection record_issues Package issues
#' @return A named list of information about packages which do not comply
#' with `DESCRPTION` checks. Each name is a package name,
Expand All @@ -18,14 +21,67 @@
#' issues <- issues_descriptions(meta = meta)
#' str(issues)
issues_descriptions <- function(meta = meta_packages()) {
meta$issue <- FALSE
meta <- issues_descriptions_advisories(meta)
meta <- issues_descriptions_remotes(meta)
fields <- "remotes"
meta <- meta[, c("package", fields)]
issues_list(meta)
meta <- meta[meta$issue,, drop = FALSE] # nolint
issues_list(meta[, c("package", "advisories", "remotes")])
}

issues_descriptions_advisories <- function(meta) {
advisories <- read_advisories()
meta <- merge(
x = meta,
y = advisories,
by = c("package", "version"),
all.x = TRUE,
all.y = FALSE
)
meta$issue <- meta$issue | !vapply(meta$advisories, anyNA, logical(1L))
meta
}

issues_descriptions_remotes <- function(meta) {
meta[["remotes"]] <- meta[["remotes"]] %||% replicate(nrow(meta), NULL)
meta$remotes <- lapply(meta$remotes, function(x) x[nzchar(x)])
meta[vapply(meta$remotes, length, integer(1L)) > 0L, ]
meta$issue <- meta$issue | vapply(meta$remotes, length, integer(1L)) > 0L
meta
}

read_advisories <- function() {
path <- tempfile()
on.exit(unlink(path, recursive = TRUE, force = TRUE))
gert::git_clone(
url = "https://github.com/RConsortium/r-advisory-database",
path = path,
verbose = FALSE
)
advisories <- list.files(
file.path(path, "vulns"),
recursive = TRUE,
full.names = TRUE
)
out <- do.call(vctrs::vec_rbind, lapply(advisories, read_advisory))
stats::aggregate(x = advisories ~ package + version, data = out, FUN = list)
}

read_advisory <- function(path) {
out <- lapply(
yaml::read_yaml(file = path)$affected,
advisory_entry,
path = path
)
do.call(vctrs::vec_rbind, out)
}

advisory_entry <- function(entry, path) {
data.frame(
package = entry$package$name,
version = entry$versions,
advisories = file.path(
"https://github.com/RConsortium/r-advisory-database/blob/main/vulns",
entry$package$name,
basename(path)
)
)
}
3 changes: 3 additions & 0 deletions R/package.R
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#' @importFrom gert git_clone
#' @importFrom gh gh
#' @importFrom igraph graph neighbors subcomponent V
#' @importFrom jsonlite parse_json read_json stream_in write_json
#' @importFrom nanonext ncurl parse_url status_code
#' @importFrom pkgsearch cran_package
#' @importFrom stats aggregate
#' @importFrom utils available.packages compareVersion
#' @importFrom vctrs vec_rbind vec_slice
#' @importFrom yaml read_yaml
NULL
4 changes: 3 additions & 1 deletion R/utils_issues.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ issues_list <- function(x) {
out <- list()
for (index in seq_len(nrow(x))) {
for (field in setdiff(colnames(x), "package")) {
out[[x$package[index]]][[field]] <- x[[field]][[index]]
if (!all(is.na(x[[field]][[index]]))) {
out[[x$package[index]]][[field]] <- x[[field]][[index]]
}
}
}
out[order(as.character(names(out)))]
Expand Down
11 changes: 8 additions & 3 deletions man/issues_descriptions.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions tests/testthat/test-issues_descriptions.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,34 @@ test_that("issues_descriptions() on a small repo", {
issues <- issues_descriptions(meta = meta)
expect_true(is.list(issues))
})

test_that("issues_descriptions() with security advisories", {
example <- mock_meta_packages$package == "nanonext"
commonmark <- mock_meta_packages[example,, drop = FALSE] # nolint
commonmark$package <- "commonmark"
commonmark$version <- "0.2"
readxl <- mock_meta_packages[example,, drop = FALSE] # nolint
readxl$package <- "readxl"
readxl$version <- "1.4.1"
meta <- rbind(mock_meta_packages, commonmark, readxl)
out <- issues_descriptions(meta)
exp <- list(
audio.whisper = list(remotes = "bnosac/audio.vadwebrtc"),
commonmark = list(
advisories = file.path(
"https://github.com/RConsortium/r-advisory-database",
"blob/main/vulns/commonmark",
c("RSEC-2023-6.yaml", "RSEC-2023-7.yaml", "RSEC-2023-8.yaml")
)
),
readxl = list(
advisories = file.path(
"https://github.com/RConsortium/r-advisory-database",
"blob/main/vulns/readxl/RSEC-2023-2.yaml"
)
),
stantargets = list(remotes = c("hyunjimoon/SBC", "stan-dev/cmdstanr")),
tidypolars = list(remotes = "markvanderloo/tinytest/pkg")
)
expect_equal(out, exp)
})
25 changes: 25 additions & 0 deletions tests/testthat/test-utils_issues.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
test_that("issues_list() handles missing and empty values correctly", {
meta <- data.frame(
package = c("audio.whisper", "readxl", "stantargets", "tidypolars"),
advisory = c(
NA_character_,
"url",
NA_character_,
NA_character_
)
)
meta$remotes <- list(
"bnosac/audio.vadwebrtc",
NULL,
c("hyunjimoon/SBC", "stan-dev/cmdstanr"),
"markvanderloo/tinytest/pkg"
)
out <- issues_list(meta)
exp <- list(
audio.whisper = list(remotes = "bnosac/audio.vadwebrtc"),
readxl = list(advisory = "url"),
stantargets = list(remotes = c("hyunjimoon/SBC", "stan-dev/cmdstanr")),
tidypolars = list(remotes = "markvanderloo/tinytest/pkg")
)
expect_equal(out, exp)
})
Loading