Skip to content

API key stored as plaintext with umask-dependent file permissions; save_api_key() steers users into a project-level `.Renviron #387

Description

@docxology

Summary

While reviewing the key-handling code, I noticed that the only key-at-rest mechanism is a plaintext DELPHI_EPIDATA_KEY=... line in .Renviron, that save_api_key() prefers a project-level .Renviron (i.e., a file inside the user's current git repository), and that nothing enforces restrictive file permissions anywhere — the file's mode is whatever the umask produces (typically 0644, world-readable). The package only advises the user to gitignore the project file, and the repo's own .gitignore excludes .env/.secrets/.Rprofile but not .Renviron. I think a few small changes (chmod 0600, confirm before writing a project-level file, a keyring pointer in the docs, and a .gitignore entry) would close the practical exposure.

Evidence (dev tip 87b10e1)

  • R/auth.R:69-77 — path choice and file creation, no chmod:
    # Prefer a project-level .Renviron in the working directory; fall back to
    # the user-level one R reads at startup (see ?Startup).
    if (file.exists(".Renviron")) {
      path <- ".Renviron"
    } else {
      path <- path.expand(file.path("~", ".Renviron"))
      if (!file.exists(path)) {
        file.create(path)
      }
    }
    utils::file.edit(path)
    On main (09da015) the equivalent is usethis::edit_r_environ(scope = "project") when usethis::proj_path(".Renviron") exists — same project-first preference.
  • The project-level advice is the only mitigation (R/auth.R:79-87): "Make sure not to share this file (add it to .gitignore or equivalents)."
  • .gitignore (repo root) lists .env, .secrets, .Rprofile, .httr-oauth — but not .Renviron.
  • Reader side: get_api_key() only reads the env var (R/auth.R:37-52); there is no keyring/encrypted-store alternative offered anywhere in the docs.

Maintainer-runnable repro

setwd(tempdir())
file.create(".Renviron")
format(file.info(".Renviron")$mode)   # "644" under a default umask of 022 (world-readable)

And the workflow risk: setwd(<a git repo>); save_api_key() opens/creates <repo>/.Renviron — an untracked file one git add -f/git add . away from being committed, with the only protection being the message the user may not read.

Expected vs. actual

  • Expected: a credential file is created/edited with owner-only permissions, and storing a key inside a project directory is at least confirmed (and ideally discouraged in favor of the user-level file or a keyring).
  • Actual: 0644-mode plaintext key file; project-level placement preferred with only advisory protection; no keyring path documented.

Suggested fix

  1. After file.create(path), apply Sys.chmod(path, "0600") (and optionally warn if an existing .Renviron is group/world-readable).
  2. Before choosing the project-level path, ask for confirmation (and mention the risk that it sits inside a git repository), or prefer the user-level file by default.
  3. Add .Renviron to the repo's own .gitignore as an example for contributors.
  4. Document a keyring-based alternative (e.g. the keyring package) in ?get_api_key for users who want encrypted storage.

I checked for prior reports (issues #181 "Figure out better API key management", #295, #339 — the last fixed a save_api_key crash, not the storage posture) and found none covering file permissions or the project-file preference.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions