Skip to content
Closed
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
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

* Cached API responses are written atomically and corrupt RDS files now fail with a specific cache-clear instruction instead of falling through to network access.
* Cached archive and adapter files without provenance receipts now fail closed when a URL, version, `as_of`, or expected digest is requested. Unconstrained internal reuse warns and records a `legacy_unverified` receipt, while `force = TRUE` replaces the legacy bytes without exposing URL credentials.
* Cached archive and adapter readers now copy verified bytes to a private snapshot while holding the cache lock, so a concurrent refresh cannot separate parsed data from its source receipt. Omitted provenance fields remain unconstrained when a verified snapshot is reused. OAuth and cloud credential parameters are matched by explicit normalized names, including authorization `code`, without hiding benign names such as `author` or `monkey`.
* Cached archive readers now acquire matching bytes and receipts and create a verified private snapshot under one cache lock, so a concurrent forced refresh cannot change the archive being parsed. Adapter readers continue to consume private verified snapshots, and omitted provenance fields remain unconstrained when a snapshot is reused. OAuth and cloud credential parameters are matched by explicit normalized names without hiding benign names such as `author`, `monkey`, or `keyboard`.
* Clean-install validation now builds a source tarball, installs it into temporary libraries outside the repository, exercises every public export with deterministic offline fixtures, and runs twice in pull-request CI.
* O*NET archive and external-adapter downloads now support optional `expected_sha256` and `as_of` verification, write atomic source receipts with URL, commit when inferable, retrieval time, digest, size, and version metadata, and reject changed or mismatched cached sources.
* Pull requests now require installed-package tests with network access blocked plus a complete pkgdown reference check and site build; deployment remains limited to pushes on `main`.
Expand Down
106 changes: 76 additions & 30 deletions R/cache.R
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,25 @@ onet_receipt_value <- function(x) {
as.character(x)
}

onet_url_parameter_is_sensitive <- function(name) {
onet_normalize_url_parameter_name <- function(name) {
name <- utils::URLdecode(name)
name <- gsub("([A-Z]+)([A-Z][a-z])", "\\1_\\2", name, perl = TRUE)
name <- gsub("([a-z0-9])([A-Z])", "\\1_\\2", name, perl = TRUE)
name <- tolower(name)
name <- gsub("[-.]", "_", name)
name <- gsub(
"([[:upper:]]+)([[:upper:]][[:lower:]])",
"\\1_\\2",
name,
perl = TRUE
)
name <- gsub(
"([[:lower:][:digit:]])([[:upper:]])",
"\\1_\\2",
name,
perl = TRUE
)
tolower(gsub("[.-]+", "_", name))
}

onet_url_parameter_is_sensitive <- function(name) {
name <- onet_normalize_url_parameter_name(name)
sensitive_names <- c(
"auth",
"authorization",
Expand All @@ -203,55 +216,61 @@ onet_url_parameter_is_sensitive <- function(name) {
"credential",
"credentials",
"key",
"access_token",
"id_token",
"refresh_token",
"token",
"api_token",
"api_key",
"apikey",
"x_api_key",
"subscription_key",
"ocp_apim_subscription_key",
"token",
"api_token",
"auth_token",
"access_token",
"id_token",
"refresh_token",
"oauth_token",
"bearer_token",
"security_token",
"session_token",
"access_key",
"access_key_id",
"secret_key",
"consumer_key",
"client_secret",
"consumer_secret",
"client_assertion",
"assertion",
"jwt",
"saml_response",
"secret",
"secret_key",
"secret_access_key",
"private_key",
"private_token",
"personal_access_token",
"password",
"passwd",
"pwd",
"signature",
"sig",
"oauth_signature",
"oauth_verifier",
"sas",
"shared_access_signature",
"access_key",
"access_key_id",
"aws_access_key_id",
"aws_secret_access_key",
"aws_session_token",
"aws_security_token",
"sas_token",
"account_key",
"storage_account_key",
"shared_access_key",
"connection_string",
"aws_access_key_id",
"aws_secret_access_key",
"aws_session_token",
"aws_security_token",
"x_amz_credential",
"x_amz_signature",
"x_amz_security_token",
"x_goog_credential",
"x_goog_signature",
"x_goog_security_token"
"x_goog_security_token",
"google_access_id"
)
name %in% sensitive_names ||
gsub("_", "", name, fixed = TRUE) %in%
Expand Down Expand Up @@ -419,6 +438,19 @@ onet_atomic_commit_source <- function(
dest,
receipt,
return_snapshot = FALSE) {
onet_with_cache_lock(dest, onet_atomic_commit_source_unlocked(
tmp = tmp,
dest = dest,
receipt = receipt,
return_snapshot = return_snapshot
))
}

onet_atomic_commit_source_unlocked <- function(
tmp,
dest,
receipt,
return_snapshot = FALSE) {
receipt_dest <- onet_receipt_path(dest)
receipt_tmp <- tempfile(
paste0(".", basename(receipt_dest), "-write-"),
Expand All @@ -441,7 +473,7 @@ onet_atomic_commit_source <- function(
}, add = TRUE)
}

result <- onet_with_cache_lock(dest, {
commit <- function() {
old_paths <- c(dest, receipt_dest)
backups <- rep(NA_character_, length(old_paths))
installed <- character()
Expand Down Expand Up @@ -504,7 +536,8 @@ onet_atomic_commit_source <- function(
} else {
invisible(dest)
}
})
}
result <- commit()
snapshot_success <- isTRUE(return_snapshot)
result
}
Expand Down Expand Up @@ -587,6 +620,21 @@ onet_cached_source_snapshot <- function(
expected_sha256 = NULL,
version = NULL,
as_of = NULL) {
onet_with_cache_lock(path, onet_cached_source_snapshot_unlocked(
path = path,
source_url = source_url,
expected_sha256 = expected_sha256,
version = version,
as_of = as_of
))
}

onet_cached_source_snapshot_unlocked <- function(
path,
source_url = NULL,
expected_sha256 = NULL,
version = NULL,
as_of = NULL) {
extension <- tools::file_ext(path)
snapshot <- tempfile(
"onet-cache-snapshot-",
Expand All @@ -599,16 +647,14 @@ onet_cached_source_snapshot <- function(
}
}, add = TRUE)

result <- onet_with_cache_lock(path, {
receipt <- onet_cached_source_receipt_unlocked(
path = path,
source_url = source_url,
expected_sha256 = expected_sha256,
version = version,
as_of = as_of
)
onet_copy_verified_snapshot(path, receipt, snapshot)
})
receipt <- onet_cached_source_receipt_unlocked(
path = path,
source_url = source_url,
expected_sha256 = expected_sha256,
version = version,
as_of = as_of
)
result <- onet_copy_verified_snapshot(path, receipt, snapshot)
success <- TRUE
result
}
Expand Down
Loading