Skip to content

[build] Create config files from environment variables for publishing#16951

Merged
titusfortner merged 3 commits intotrunkfrom
pr/rakefile-config-publishing
Jan 19, 2026
Merged

[build] Create config files from environment variables for publishing#16951
titusfortner merged 3 commits intotrunkfrom
pr/rakefile-config-publishing

Conversation

@titusfortner
Copy link
Member

@titusfortner titusfortner commented Jan 19, 2026

User description

Ruby, Python & JS all failed automated release process yesterday on CI, but passed with same code when run locally.
I think it was probably environment variable related?

💥 What does this PR do?

  • TWINE_USERNAME is always token
  • Create config files from environment variables for publishing

🔧 Implementation Notes

Does not override existing credentials when found if you're running locally with your own

💡 Additional Considerations

We can't know if this will work until next release, unfortunately

🔄 Types of changes

  • Bug fix (backwards compatible)

PR Type

Bug fix, Enhancement


Description

  • Create config files from environment variables for publishing

  • Replace credential validation with dynamic setup functions

  • Set TWINE_USERNAME to token for PyPI authentication

  • Skip credential checks for nightly releases


Diagram Walkthrough

flowchart LR
  A["Environment Variables"] -->|setup_npm_auth| B[".npmrc"]
  A -->|setup_gem_credentials| C[".gem/credentials"]
  A -->|setup_pypirc| D[".pypirc"]
  B --> E["Node Release"]
  C --> F["Ruby Release"]
  D --> G["Python Release"]
Loading

File Walkthrough

Relevant files
Configuration changes
bazel.yml
Remove TWINE_USERNAME from workflow secrets                           

.github/workflows/bazel.yml

  • Removed TWINE_USERNAME from GitHub Actions environment variables
  • TWINE_USERNAME will now be hardcoded as __token__ in the Rakefile
    setup
+0/-1     
Bug fix
Rakefile
Replace credential validation with dynamic config file setup

Rakefile

  • Removed python, ruby, and node credential validation entries from
    RELEASE_CREDENTIALS
  • Added setup_npm_auth function to create .npmrc from NODE_AUTH_TOKEN
    environment variable
  • Added setup_gem_credentials function to create .gem/credentials from
    GEM_HOST_API_KEY
  • Added setup_pypirc function to create .pypirc with __token__ username
    from TWINE_PASSWORD
  • Updated node:release, py:release, and rb:release tasks to call setup
    functions instead of credential validation
+58/-15 

@selenium-ci selenium-ci added the B-build Includes scripting, bazel and CI integrations label Jan 19, 2026
@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 19, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Secret file permissions

Description: setup_npm_auth writes the npm auth token into ~/.npmrc but does not enforce restrictive
file permissions (unlike setup_gem_credentials/setup_pypirc), which can leave the token
readable by other users on the machine depending on existing file permissions/umask.
Rakefile [406-418]

Referred Code
def setup_npm_auth
  npmrc = File.join(Dir.home, '.npmrc')
  return if File.exist?(npmrc) && File.read(npmrc).include?('//registry.npmjs.org/:_authToken=')

  token = ENV.fetch('NODE_AUTH_TOKEN', nil)
  raise 'Missing npm credentials: set NODE_AUTH_TOKEN or configure ~/.npmrc' if token.nil? || token.empty?

  auth_line = "//registry.npmjs.org/:_authToken=#{token}"
  if File.exist?(npmrc)
    File.open(npmrc, 'a') { |f| f.puts(auth_line) }
  else
    File.write(npmrc, "#{auth_line}\n")
  end
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Insecure .npmrc perms: setup_npm_auth writes the npm auth token to ~/.npmrc but does not enforce restrictive file
permissions (unlike the RubyGems and PyPI credential files), risking unintended secret
exposure via permissive default umask or existing file permissions.

Referred Code
def setup_npm_auth
  npmrc = File.join(Dir.home, '.npmrc')
  return if File.exist?(npmrc) && File.read(npmrc).include?('//registry.npmjs.org/:_authToken=')

  token = ENV.fetch('NODE_AUTH_TOKEN', nil)
  raise 'Missing npm credentials: set NODE_AUTH_TOKEN or configure ~/.npmrc' if token.nil? || token.empty?

  auth_line = "//registry.npmjs.org/:_authToken=#{token}"
  if File.exist?(npmrc)
    File.open(npmrc, 'a') { |f| f.puts(auth_line) }
  else
    File.write(npmrc, "#{auth_line}\n")
  end
end

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No audit logging: The new publishing setup functions write sensitive credential files (e.g., /.npmrc,
/.gem/credentials, ~/.pypirc) without any audit logging of the action/outcome, which may
hinder reconstruction of credential-related publishing events depending on policy.

Referred Code
def setup_npm_auth
  npmrc = File.join(Dir.home, '.npmrc')
  return if File.exist?(npmrc) && File.read(npmrc).include?('//registry.npmjs.org/:_authToken=')

  token = ENV.fetch('NODE_AUTH_TOKEN', nil)
  raise 'Missing npm credentials: set NODE_AUTH_TOKEN or configure ~/.npmrc' if token.nil? || token.empty?

  auth_line = "//registry.npmjs.org/:_authToken=#{token}"
  if File.exist?(npmrc)
    File.open(npmrc, 'a') { |f| f.puts(auth_line) }
  else
    File.write(npmrc, "#{auth_line}\n")
  end
end

def setup_gem_credentials
  gem_dir = File.join(Dir.home, '.gem')
  credentials = File.join(gem_dir, 'credentials')
  return if File.exist?(credentials) && File.read(credentials).include?(':rubygems_api_key:')

  token = ENV.fetch('GEM_HOST_API_KEY', nil)


 ... (clipped 33 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unhandled IO errors: The new setup functions perform multiple file reads/writes (e.g., File.read, File.write,
File.open, File.chmod) without handling common IO/permission failures, which could cause
non-actionable task crashes in restricted CI environments.

Referred Code
def setup_npm_auth
  npmrc = File.join(Dir.home, '.npmrc')
  return if File.exist?(npmrc) && File.read(npmrc).include?('//registry.npmjs.org/:_authToken=')

  token = ENV.fetch('NODE_AUTH_TOKEN', nil)
  raise 'Missing npm credentials: set NODE_AUTH_TOKEN or configure ~/.npmrc' if token.nil? || token.empty?

  auth_line = "//registry.npmjs.org/:_authToken=#{token}"
  if File.exist?(npmrc)
    File.open(npmrc, 'a') { |f| f.puts(auth_line) }
  else
    File.write(npmrc, "#{auth_line}\n")
  end
end

def setup_gem_credentials
  gem_dir = File.join(Dir.home, '.gem')
  credentials = File.join(gem_dir, 'credentials')
  return if File.exist?(credentials) && File.read(credentials).include?(':rubygems_api_key:')

  token = ENV.fetch('GEM_HOST_API_KEY', nil)


 ... (clipped 33 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 19, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Secure .npmrc file permissions
Suggestion Impact:Added `File.chmod(0o600, npmrc)` after writing to the .npmrc file to restrict permissions and protect the token.

code diff:

@@ -416,6 +416,7 @@
   else
     File.write(npmrc, "#{auth_line}\n")
   end
+  File.chmod(0o600, npmrc)
 end

Set the file permissions of .npmrc to 0o600 after writing to it to protect the
contained credentials.

Rakefile [414-418]

 if File.exist?(npmrc)
   File.open(npmrc, 'a') { |f| f.puts(auth_line) }
 else
   File.write(npmrc, "#{auth_line}\n")
 end
+File.chmod(0o600, npmrc)

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: This is a critical security suggestion, as the .npmrc file contains a secret token and its permissions should be restricted to the owner, which is a standard practice demonstrated elsewhere in the same file for other credential files.

Medium
General
Prevent duplicate pypirc sections

Improve the check for an existing .pypirc configuration to also verify the
presence of username and password fields before skipping, preventing duplicate
entries.

Rakefile [442]

-return if File.exist?(pypirc) && File.read(pypirc).match?(/^\[pypi\]/m)
+return if File.exist?(pypirc) && File.read(pypirc).match?(/^\[pypi\].*username\s*=\s*__token__.*password\s*=/m)
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the current check is too lenient and could be improved to prevent adding duplicate [pypi] sections if one already exists but with different content, making the script more robust.

Medium
Possible issue
Ensure correct config file formatting

Replace f.puts with f.write when appending to .pypirc to avoid adding extra
newlines from the heredoc and ensure correct file formatting.

Rakefile [453-457]

 if File.exist?(pypirc)
-  File.open(pypirc, 'a') { |f| f.puts("\n#{pypi_section}") }
+  File.open(pypirc, 'a') { |f| f.write("\n#{pypi_section}") }
 else
   File.write(pypirc, pypi_section)
 end
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that using puts with a heredoc that already contains newlines can lead to extra, unintended blank lines in the config file, and proposes a more precise way to write the content.

Low
Learned
best practice
Validate and sanitize env inputs

Strip and validate env var values (e.g., reject whitespace-only and
newline-containing tokens) before interpolating into config files to avoid
malformed configs or token-injection via newlines.

Rakefile [410-413]

-token = ENV.fetch('NODE_AUTH_TOKEN', nil)
+token = ENV.fetch('NODE_AUTH_TOKEN', nil)&.strip
 raise 'Missing npm credentials: set NODE_AUTH_TOKEN or configure ~/.npmrc' if token.nil? || token.empty?
+raise 'Invalid NODE_AUTH_TOKEN: must not contain newlines' if token.match?(/[\r\n]/)
 
 auth_line = "//registry.npmjs.org/:_authToken=#{token}"
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add explicit validation/sanitization guards at integration boundaries (environment variables, file content) before using them.

Low
  • Update

@titusfortner titusfortner requested review from Copilot and p0deje and removed request for p0deje January 19, 2026 17:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes automated release process failures by creating configuration files from environment variables for publishing packages to npm, RubyGems, and PyPI. The change addresses environment variable issues that caused Ruby, Python, and JavaScript releases to fail on CI.

Changes:

  • Hardcodes TWINE_USERNAME as __token__ for PyPI token authentication
  • Replaces static credential validation with dynamic setup functions that create config files from environment variables
  • Removes TWINE_USERNAME from GitHub Actions workflow since it's now hardcoded

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
Rakefile Replaced credential validation entries with three new setup functions (setup_npm_auth, setup_gem_credentials, setup_pypirc) that dynamically create config files from environment variables. Updated release tasks to call setup functions instead of validation.
.github/workflows/bazel.yml Removed TWINE_USERNAME environment variable since it's now hardcoded as token in the setup_pypirc function.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@titusfortner titusfortner merged commit e00b305 into trunk Jan 19, 2026
20 checks passed
@titusfortner titusfortner deleted the pr/rakefile-config-publishing branch January 19, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-build Includes scripting, bazel and CI integrations Review effort 3/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants