Skip to content

fix(security): use OsRng for all security-critical key and token generation#519

Merged
ilblackdragon merged 3 commits intonearai:mainfrom
difflabai:fix/h1-osrng-key-material
Mar 6, 2026
Merged

fix(security): use OsRng for all security-critical key and token generation#519
ilblackdragon merged 3 commits intonearai:mainfrom
difflabai:fix/h1-osrng-key-material

Conversation

@gabehamilton
Copy link
Copy Markdown
Contributor

@gabehamilton gabehamilton commented Mar 4, 2026

Summary

Switches all security-critical key, token, and secret generation from rand::thread_rng() (userspace CSPRNG) to direct OS entropy via rand::rngs::OsRng.

While thread_rng() uses a ChaCha12 CSPRNG seeded from OsRng, using OsRng directly for key material is industry best practice: it eliminates the PRNG intermediary, removes seed state that could theoretically be extracted from memory, and makes the entropy source explicit in the code.

Files changed

File What changed
src/secrets/keychain.rs Master encryption key generation
src/secrets/crypto.rs Per-secret HKDF salt generation; removed shadowing inner import
src/orchestrator/auth.rs Per-job bearer token generation
src/channels/web/mod.rs Gateway auth token fallback generation
src/cli/oauth_defaults.rs PKCE verifier + CSRF state for CLI OAuth
src/tools/mcp/auth.rs MCP OAuth PKCE verifier
src/extensions/manager.rs Auto-generated extension/channel secrets
src/setup/channels.rs Webhook secret generation (bonus find)
src/pairing/store.rs Pairing auth code generation (missed in initial pass, fixed per review)

Intentionally unchanged

  • src/llm/retry.rsthread_rng() for jitter calculation (not security-critical)
  • src/pairing/store.rs human-readable suffix fallback was originally left, now fixed per reviewer feedback

Token format note

The gateway fallback token (generated when GATEWAY_AUTH_TOKEN is not set) changed from a 32-character alphanumeric string (~190 bits of entropy from Alphanumeric distribution) to a 64-character lowercase hex string (256 bits of raw entropy). This is strictly better. Operators with external tooling that validates the token format or assumes a 32-character length should regenerate their token after updating.

Tests

  • Added 3 regression tests for generate_salt(): correct length, non-zero output, uniqueness
  • All 1,853 existing tests pass, zero clippy warnings

…ration

Replace rand::thread_rng() with rand::rngs::OsRng in all security-critical
code paths that generate cryptographic key material, bearer tokens, PKCE
verifiers, CSRF state parameters, and webhook secrets. thread_rng() uses a
userspace CSPRNG (ChaCha) seeded from OS entropy, which is fine for
non-security contexts but adds an unnecessary intermediate layer for
key material where direct OS entropy (OsRng) is the correct choice.

Files changed:
- src/secrets/keychain.rs: master encryption key generation
- src/secrets/crypto.rs: per-secret HKDF salt generation
- src/orchestrator/auth.rs: per-job bearer token generation
- src/channels/web/mod.rs: gateway auth token fallback
- src/cli/oauth_defaults.rs: OAuth PKCE verifier and CSRF state
- src/tools/mcp/auth.rs: MCP OAuth PKCE verifier
- src/extensions/manager.rs: auto-generated extension secrets
- src/setup/channels.rs: webhook secret generation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 4, 2026 03:22
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions github-actions Bot added scope: channel/cli TUI / CLI channel scope: channel/web Web gateway channel scope: tool/mcp MCP client scope: orchestrator Container orchestrator scope: secrets Secrets management scope: extensions Extension management scope: setup Onboarding / setup size: S 10-49 changed lines risk: high Safety, secrets, auth, or critical infrastructure contributor: new First-time contributor labels Mar 4, 2026
Copy link
Copy Markdown
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 strengthens security-critical randomness by switching key/token/secret generation from rand::thread_rng() (userspace CSPRNG) to direct OS entropy via rand::rngs::OsRng across the codebase’s auth, secrets, and setup flows.

Changes:

  • Use OsRng for PKCE verifiers + CSRF state generation in CLI OAuth flows and MCP OAuth.
  • Use OsRng for cryptographic salt/key/token generation in secrets + orchestrator auth.
  • Use OsRng for auto-generated extension secrets and webhook secret generation.

Reviewed changes

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

Show a summary per file
File Description
src/secrets/keychain.rs Use OsRng for master key generation
src/secrets/crypto.rs Use OS entropy for per-secret salt generation
src/orchestrator/auth.rs Use OsRng for per-job bearer token bytes
src/channels/web/mod.rs Use OsRng for gateway auth token fallback generation
src/cli/oauth_defaults.rs Use OsRng for PKCE verifier + CSRF state
src/tools/mcp/auth.rs Use OsRng for MCP PKCE verifier generation
src/extensions/manager.rs Use OsRng for auto-generated extension/channel secrets
src/setup/channels.rs Use OsRng for webhook secret generation helper

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/secrets/crypto.rs Outdated
Comment on lines 61 to 64
use rand::rngs::OsRng;
let mut salt = vec![0u8; SALT_SIZE];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut salt);
rand::RngCore::fill_bytes(&mut OsRng, &mut salt);
salt
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

In this module OsRng is already imported from aes_gcm::aead at the top of the file, but generate_salt() introduces a second OsRng via use rand::rngs::OsRng; that shadows the existing one. This makes it hard to tell which RNG type is being used in different methods (and can become problematic if rand_core versions diverge). Prefer using the already-imported aes_gcm::aead::OsRng for salt generation as well, or alias one of the imports (e.g., RandOsRng) to avoid shadowing and keep the RNG choice explicit.

Copilot uses AI. Check for mistakes.
@ilblackdragon
Copy link
Copy Markdown
Member

Good mechanical fix — switching to OsRng across all security-critical callsites is the right move.

Two notes:

  1. Missing callsite in src/pairing/store.rs: Lines 147 and 157 still use rand::thread_rng() for pairing code generation. These are security-relevant (channel authentication codes) and should also use OsRng.

  2. src/llm/retry.rs:60 uses thread_rng() for jitter calculation — that's fine, it's not security-critical.

Otherwise looks ready to merge once the pairing store callsites are added.

zmanian
zmanian previously requested changes Mar 4, 2026
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Review

The security reasoning is sound. While thread_rng() is a ChaCha12 CSPRNG seeded from OS entropy (not broken), using OsRng directly for key material is industry best practice — eliminates the PRNG intermediary and leaves no seed state to extract from memory. The scope is well-chosen: security-critical paths changed, non-security uses (jitter, human-readable codes) correctly left alone.

Required before merge

1. OsRng import shadowing in src/secrets/crypto.rs (Low)

The file already imports OsRng at the module level via aes_gcm::aead::OsRng. The PR adds a scoped use rand::rngs::OsRng inside generate_salt(). Both are re-exports of the same rand_core type today, but they come from different crate paths and could diverge. Remove the inner import and use the already-in-scope OsRng:

pub fn generate_salt() -> Vec<u8> {
    let mut salt = vec![0u8; SALT_SIZE];
    rand::RngCore::fill_bytes(&mut OsRng, &mut salt); // uses module-level import
    salt
}

Should fix

2. Acknowledge token format change — Gateway fallback token changed from 32-char alphanumeric (~190 bits) to 64-char hex (256 bits). This is strictly better entropy but changes the format. Worth noting in the PR description in case any tooling validates token format/length.

3. Regression tests — Per CLAUDE.md policy, the changed functions should have tests verifying output is non-zero and correct length (would catch a broken RNG). Or use [skip-regression-check] with justification.

- Remove shadowing inner `use rand::rngs::OsRng` in `generate_salt()`;
  use module-level `aes_gcm::aead::OsRng` import instead (same type,
  avoids divergence risk if rand_core versions drift)
- Fix missed callsites in `pairing/store.rs`: `random_code()` and
  `generate_unique_code()` now use `OsRng` for pairing auth codes
- Add regression tests for `generate_salt()`: correct length,
  non-zero output, uniqueness across calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added scope: pairing Pairing mode size: M 50-199 changed lines contributor: regular 2-5 merged PRs and removed size: S 10-49 changed lines contributor: new First-time contributor labels Mar 5, 2026
Copilot AI review requested due to automatic review settings March 6, 2026 02:22
Copy link
Copy Markdown
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

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ilblackdragon ilblackdragon merged commit 6a2a6cd into nearai:main Mar 6, 2026
18 of 19 checks passed
This was referenced Mar 6, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…ration (nearai#519)

* fix(security): use OsRng for all security-critical key and token generation

Replace rand::thread_rng() with rand::rngs::OsRng in all security-critical
code paths that generate cryptographic key material, bearer tokens, PKCE
verifiers, CSRF state parameters, and webhook secrets. thread_rng() uses a
userspace CSPRNG (ChaCha) seeded from OS entropy, which is fine for
non-security contexts but adds an unnecessary intermediate layer for
key material where direct OS entropy (OsRng) is the correct choice.

Files changed:
- src/secrets/keychain.rs: master encryption key generation
- src/secrets/crypto.rs: per-secret HKDF salt generation
- src/orchestrator/auth.rs: per-job bearer token generation
- src/channels/web/mod.rs: gateway auth token fallback
- src/cli/oauth_defaults.rs: OAuth PKCE verifier and CSRF state
- src/tools/mcp/auth.rs: MCP OAuth PKCE verifier
- src/extensions/manager.rs: auto-generated extension secrets
- src/setup/channels.rs: webhook secret generation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): address PR review feedback for OsRng migration

- Remove shadowing inner `use rand::rngs::OsRng` in `generate_salt()`;
  use module-level `aes_gcm::aead::OsRng` import instead (same type,
  avoids divergence risk if rand_core versions drift)
- Fix missed callsites in `pairing/store.rs`: `random_code()` and
  `generate_unique_code()` now use `OsRng` for pairing auth codes
- Add regression tests for `generate_salt()`: correct length,
  non-zero output, uniqueness across calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: regular 2-5 merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: channel/cli TUI / CLI channel scope: channel/web Web gateway channel scope: extensions Extension management scope: orchestrator Container orchestrator scope: pairing Pairing mode scope: secrets Secrets management scope: setup Onboarding / setup scope: tool/mcp MCP client size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants