├── .gitignore ├── LICENSE ├── tests ├── testthat.R └── testthat │ └── test-safe_install.R ├── NAMESPACE ├── .Rbuildignore ├── .travis.yml ├── codecov.yml ├── R ├── safeinstall-package.R └── safe_install.R ├── safeinstall.Rproj ├── man ├── safeinstall-package.Rd ├── check_package.Rd └── safe_install_github.Rd ├── DESCRIPTION ├── LICENSE.md ├── README.Rmd └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: Francisco Rodriguez-Sanchez 3 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(safeinstall) 3 | 4 | test_check("safeinstall") 5 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(check_package) 4 | export(safe_install_github) 5 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^safeinstall\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^\.travis\.yml$ 6 | ^codecov\.yml$ 7 | -------------------------------------------------------------------------------- /tests/testthat/test-safe_install.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("safe_install of testevil gives error", { 3 | expect_error(safe_install_github("ropenscilabs/testevil")) 4 | }) 5 | 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | cache: packages 5 | 6 | after_success: 7 | - Rscript -e 'covr::codecov()' 8 | 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | -------------------------------------------------------------------------------- /R/safeinstall-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | # The following block is used by usethis to automatically manage 5 | # roxygen namespace tags. Modify with care! 6 | ## usethis namespace: start 7 | ## usethis namespace: end 8 | NULL 9 | -------------------------------------------------------------------------------- /safeinstall.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /man/safeinstall-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/safeinstall-package.R 3 | \docType{package} 4 | \name{safeinstall-package} 5 | \alias{safeinstall} 6 | \alias{safeinstall-package} 7 | \title{safeinstall: Run some basic security checks before installing R packages from online repositories} 8 | \description{ 9 | Run some basic security checks before installing R packages from online repositories. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/Pakillo/safeinstall} 15 | \item Report bugs at \url{https://github.com/Pakillo/safeinstall/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Francisco Rodriguez-Sanchez \email{f.rodriguez.sanc@gmail.com} 21 | 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: safeinstall 2 | Title: Run some basic security checks before installing R packages from online repositories 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person(given = "Francisco", 6 | family = "Rodriguez-Sanchez", 7 | role = c("aut", "cre"), 8 | email = "f.rodriguez.sanc@gmail.com") 9 | Description: Run some basic security checks before installing R packages from online repositories. 10 | License: MIT + file LICENSE 11 | Encoding: UTF-8 12 | LazyData: true 13 | Roxygen: list(markdown = TRUE) 14 | RoxygenNote: 6.1.1 15 | URL: https://github.com/Pakillo/safeinstall 16 | BugReports: https://github.com/Pakillo/safeinstall/issues 17 | Imports: 18 | git2r, 19 | remotes, 20 | defender (>= 0.0.1.9000) 21 | Remotes: 22 | ropenscilabs/defender 23 | Suggests: 24 | testthat (>= 2.1.0), 25 | covr 26 | -------------------------------------------------------------------------------- /man/check_package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/safe_install.R 3 | \name{check_package} 4 | \alias{check_package} 5 | \title{Check a package repository for potential security issues} 6 | \usage{ 7 | check_package(repo = NULL, verbose = TRUE) 8 | } 9 | \arguments{ 10 | \item{repo}{Link to an online git repository (e.g. "https://github.com/Pakillo/safeinstall.git")} 11 | 12 | \item{verbose}{Logical. Show verbose output?} 13 | } 14 | \value{ 15 | TRUE if the package passes the security checks, otherwise FALSE (and some extra information about potential issues if verbose = TRUE). 16 | } 17 | \description{ 18 | Check a package repository for potential security issues 19 | } 20 | \examples{ 21 | \dontrun{ 22 | library(safeinstall) 23 | check_package("https://github.com/Pakillo/safeinstall.git") 24 | check_package("https://github.com/ropenscilabs/testevil.git") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /man/safe_install_github.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/safe_install.R 3 | \name{safe_install_github} 4 | \alias{safe_install_github} 5 | \title{Safe installation of R packages from GitHub} 6 | \usage{ 7 | safe_install_github(user.repo = NULL, ..., verbose = TRUE) 8 | } 9 | \arguments{ 10 | \item{user.repo}{Repository address in the format \code{username/repo}.} 11 | 12 | \item{...}{Further options passed to \code{\link[remotes:install_github]{remotes::install_github()}}.} 13 | 14 | \item{verbose}{Logical. Show verbose output?} 15 | } 16 | \value{ 17 | If no unsafe code is detected, the package is installed. Otherwise, an error message calling to inspect the repository for potential problems. 18 | } 19 | \description{ 20 | Safe installation of R packages from GitHub 21 | } 22 | \examples{ 23 | \dontrun{ 24 | library(safeinstall) 25 | safe_install_github("Pakillo/safeinstall") 26 | safe_install_github("ropenscilabs/testevil") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 Francisco Rodriguez-Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | # safeinstall 16 | 17 | 18 | [![Travis build status](https://travis-ci.org/Pakillo/safeinstall.svg?branch=master)](https://travis-ci.org/Pakillo/safeinstall) 19 | [![Codecov test coverage](https://codecov.io/gh/Pakillo/safeinstall/branch/master/graph/badge.svg)](https://codecov.io/gh/Pakillo/safeinstall?branch=master) 20 | 21 | 22 | The goal of `safeinstall` is to run some basic security checks before installing an R package from an online repository (like GitHub). This package is basically a wrapper of [defender](https://github.com/ropenscilabs/defender) so that some security checks are done before running `remotes::install_github`. 23 | 24 | ## Installation 25 | 26 | ``` r 27 | remotes::install_github("Pakillo/safeinstall") 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```{r} 33 | library(safeinstall) 34 | ``` 35 | 36 | `safe_install_github` is like `remotes::install_github` but will run some checks before installing the package. If nothing wrong is detected, the package is installed. 37 | 38 | ```{r eval=FALSE} 39 | safe_install_github("Pakillo/safeinstall") 40 | ``` 41 | 42 | In contrast, if potentially unsafe code is detected (e.g. system calls), the package will not install and an error will be returned. 43 | 44 | ```{r eval=TRUE, error=TRUE} 45 | safe_install_github("ropenscilabs/testevil") 46 | ``` 47 | 48 | 49 | ### Checking any git repository 50 | 51 | To just scan a package (without installing), run `check_package` on the git repository: 52 | 53 | ```{r} 54 | check_package("https://github.com/Pakillo/safeinstall.git") 55 | ``` 56 | 57 | Will return TRUE if no problem is found, or FALSE (plus info on potential problems) otherwise: 58 | 59 | ```{r} 60 | check_package("https://github.com/ropenscilabs/testevil.git") 61 | ``` 62 | 63 | This works with any git repository (not only GitHub), e.g. on Gitlab: 64 | 65 | ```{r} 66 | check_package("https://gitlab.com/jimhester/covr.git") 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /R/safe_install.R: -------------------------------------------------------------------------------- 1 | #' Safe installation of R packages from GitHub 2 | #' 3 | #' @param user.repo Repository address in the format `username/repo`. 4 | #' @param ... Further options passed to [remotes::install_github()]. 5 | #' @param verbose Logical. Show verbose output? 6 | #' 7 | #' @return If no unsafe code is detected, the package is installed. Otherwise, an error message calling to inspect the repository for potential problems. 8 | #' @export 9 | #' 10 | #' @examples 11 | #' \dontrun{ 12 | #' library(safeinstall) 13 | #' safe_install_github("Pakillo/safeinstall") 14 | #' safe_install_github("ropenscilabs/testevil") 15 | #' } 16 | 17 | safe_install_github <- function(user.repo = NULL, ..., verbose = TRUE) { 18 | 19 | git.repo <- paste0("https://github.com/", user.repo, ".git") 20 | test.passed <- check_package(git.repo, verbose = verbose) 21 | 22 | if (isTRUE(test.passed)) { 23 | remotes::install_github(user.repo, ...) 24 | } else { 25 | stop("There might be some unsafe code in this repository. It is advised to inspect it more thoroughly before installing (e.g. try defender package: https://github.com/ropenscilabs/defender).") 26 | } 27 | 28 | } 29 | 30 | 31 | #' Check a package repository for potential security issues 32 | #' 33 | #' @param repo Link to an online git repository (e.g. "https://github.com/Pakillo/safeinstall.git") 34 | #' @param verbose Logical. Show verbose output? 35 | #' 36 | #' @return TRUE if the package passes the security checks, otherwise FALSE (and some extra information about potential issues if verbose = TRUE). 37 | #' @export 38 | #' 39 | #' @examples 40 | #' \dontrun{ 41 | #' library(safeinstall) 42 | #' check_package("https://github.com/Pakillo/safeinstall.git") 43 | #' check_package("https://github.com/ropenscilabs/testevil.git") 44 | #' } 45 | check_package <- function(repo = NULL, verbose = TRUE) { 46 | 47 | # clone repo locally 48 | temp.dir <- "safetest" 49 | git2r::clone(repo, local_path = temp.dir, progress = FALSE) 50 | 51 | ## run defender tests 52 | 53 | # system calls 54 | test1 <- defender::summarize_system_calls(temp.dir) 55 | 56 | if (nrow(test1) > 0 & isTRUE(verbose)) { 57 | message("\n\nSystem calls found:\n") 58 | print(test1) 59 | } 60 | 61 | # dangerous imports 62 | test2 <- defender::check_namespace(temp.dir) 63 | 64 | if (nrow(test2) > 0 & isTRUE(verbose)) { 65 | message("\n\nDangerous imports found:\n") 66 | print(test2) 67 | } 68 | 69 | # delete temp dir 70 | unlink(temp.dir, recursive = TRUE, force = TRUE) 71 | 72 | # if there are zero rows, defender did not detect any problem 73 | if (any(nrow(test1), nrow(test2)) > 0) { 74 | test.passed <- FALSE 75 | } else { 76 | test.passed <- TRUE 77 | } 78 | 79 | test.passed 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # safeinstall 5 | 6 | 7 | 8 | [![Travis build 9 | status](https://travis-ci.org/Pakillo/safeinstall.svg?branch=master)](https://travis-ci.org/Pakillo/safeinstall) 10 | [![Codecov test 11 | coverage](https://codecov.io/gh/Pakillo/safeinstall/branch/master/graph/badge.svg)](https://codecov.io/gh/Pakillo/safeinstall?branch=master) 12 | 13 | 14 | The goal of `safeinstall` is to run some basic security checks before 15 | installing an R package from an online repository (like GitHub). This 16 | package is basically a wrapper of 17 | [defender](https://github.com/ropenscilabs/defender) so that some 18 | security checks are done before running `remotes::install_github`. 19 | 20 | ## Installation 21 | 22 | ``` r 23 | remotes::install_github("Pakillo/safeinstall") 24 | ``` 25 | 26 | ## Usage 27 | 28 | ``` r 29 | library(safeinstall) 30 | ``` 31 | 32 | `safe_install_github` is like `remotes::install_github` but will run 33 | some checks before installing the package. If nothing wrong is detected, 34 | the package is installed. 35 | 36 | ``` r 37 | safe_install_github("Pakillo/safeinstall") 38 | ``` 39 | 40 | In contrast, if potentially unsafe code is detected (e.g. system calls), 41 | the package will not install and an error will be returned. 42 | 43 | ``` r 44 | safe_install_github("ropenscilabs/testevil") 45 | #> 46 | #> 47 | #> System calls found: 48 | #> path line_number call 49 | #> 1 inst/root_sys.R 1 system2("ls") 50 | #> 2 inst/root_sys.R 4 system("ls") 51 | #> 3 R/exported.R 7 system2("ls") 52 | #> 4 R/internal.R 4 system("ls") 53 | #> 5 R/internal.R 8 system("ls -la") 54 | #> 6 R/processx.R 3 processx::run("ls") 55 | #> 7 R/sys.R 8 sys::exec_internal("ls") 56 | #> 8 R/system_hidden.R 2 system2("lm") 57 | #> function_name 58 | #> 1 system2 59 | #> 2 system 60 | #> 3 system2 61 | #> 4 system 62 | #> 5 system 63 | #> 6 processx::run 64 | #> 7 sys::exec_internal 65 | #> 8 system2 66 | #> 67 | #> 68 | #> Dangerous imports found: 69 | #> type import package 70 | #> 1 package sys sys 71 | #> 2 package processx processx 72 | #> 3 function processx::run processx 73 | #> Error in safe_install_github("ropenscilabs/testevil"): There might be some unsafe code in this repository. It is advised to inspect it more thoroughly before installing (e.g. try defender package: https://github.com/ropenscilabs/defender). 74 | ``` 75 | 76 | ### Checking any git repository 77 | 78 | To just scan a package (without installing), run `check_package` on the 79 | git repository: 80 | 81 | ``` r 82 | check_package("https://github.com/Pakillo/safeinstall.git") 83 | #> [1] TRUE 84 | ``` 85 | 86 | Will return TRUE if no problem is found, or FALSE (plus info on 87 | potential problems) otherwise: 88 | 89 | ``` r 90 | check_package("https://github.com/ropenscilabs/testevil.git") 91 | #> 92 | #> 93 | #> System calls found: 94 | #> path line_number call 95 | #> 1 inst/root_sys.R 1 system2("ls") 96 | #> 2 inst/root_sys.R 4 system("ls") 97 | #> 3 R/exported.R 7 system2("ls") 98 | #> 4 R/internal.R 4 system("ls") 99 | #> 5 R/internal.R 8 system("ls -la") 100 | #> 6 R/processx.R 3 processx::run("ls") 101 | #> 7 R/sys.R 8 sys::exec_internal("ls") 102 | #> 8 R/system_hidden.R 2 system2("lm") 103 | #> function_name 104 | #> 1 system2 105 | #> 2 system 106 | #> 3 system2 107 | #> 4 system 108 | #> 5 system 109 | #> 6 processx::run 110 | #> 7 sys::exec_internal 111 | #> 8 system2 112 | #> 113 | #> 114 | #> Dangerous imports found: 115 | #> type import package 116 | #> 1 package sys sys 117 | #> 2 package processx processx 118 | #> 3 function processx::run processx 119 | #> [1] FALSE 120 | ``` 121 | 122 | This works with any git repository (not only GitHub), e.g. on Gitlab: 123 | 124 | ``` r 125 | check_package("https://gitlab.com/jimhester/covr.git") 126 | #> 127 | #> 128 | #> System calls found: 129 | #> path line_number 130 | #> 1 R/covr.R 332 131 | #> 2 R/covr.R 349 132 | #> 3 R/system.R 23 133 | #> 4 R/system.R 49 134 | #> call 135 | #> 1 system(cmd) 136 | #> 2 system(cmd) 137 | #> 3 system(full, intern = FALSE, ignore.stderr = quiet, ignore.stdout = quiet, ...) 138 | #> 4 system(full, intern = TRUE, ignore.stderr = quiet, ...) 139 | #> function_name 140 | #> 1 system 141 | #> 2 system 142 | #> 3 system 143 | #> 4 system 144 | #> [1] FALSE 145 | ``` 146 | --------------------------------------------------------------------------------