Skip to content

Eliminate hardcoded JWT secrets; auto-generate and persist on first boot#75

Merged
unnamedlab merged 2 commits into
mainfrom
copilot/analyze-code-for-bugs
Apr 30, 2026
Merged

Eliminate hardcoded JWT secrets; auto-generate and persist on first boot#75
unnamedlab merged 2 commits into
mainfrom
copilot/analyze-code-for-bugs

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 30, 2026

A prior audit flagged five JwtConfig::new("secret"|"test-secret"|...) literals across the workspace and asked for an unattended secret-bootstrap path that does not rely on .env files.

libs/auth-middleware

  • JwtConfig::generate() — 256-bit HS256 secret from rand::thread_rng().
  • JwtConfig::load_or_generate(path) — reads/creates a hex-encoded secret file with 0600 perms (parent dir 0700) on Unix; idempotent across restarts so existing tokens stay valid.
  • JwtConfig::resolve_unattended(default_path) — precedence OPENFOUNDRY_JWT_SECRETOPENFOUNDRY_JWT_SECRET_PATHdefault_path.
  • New SecretLoadError; tolerates raw-bytes secrets seeded by external KMS.

Literal removal

Replaced every hardcoded literal with JwtConfig::generate() (and the data-asset-catalog-service test fixture with Uuid::now_v7().to_string(), since that field is never used as a signing secret in the test). Also fixed five duplicates of the same pattern not in the original report (pipeline-authoring-service, report-service, edge-gateway rate-limit tests ×4, internal auth-middleware tests ×2).

The edge-gateway token() test helper now takes &JwtConfig instead of &str so the issuer and verifier share the same generated key.

Unattended production path (ingestion-replication-service)

  • AppConfig.jwt_secret is now Option<String> (#[serde(default)]).
  • New AppConfig.jwt_secret_path, default /var/lib/openfoundry/ingestion-replication-service/jwt.secret.
  • main.rs falls back to JwtConfig::resolve_unattended(...) when no secret is configured.
let jwt_config = match config.jwt_secret.as_deref().map(str::trim) {
    Some(secret) if !secret.is_empty() => JwtConfig::new(secret).with_env_defaults(),
    _ => JwtConfig::resolve_unattended(&config.jwt_secret_path)?
        .with_env_defaults(),
};

Notes for reviewers

  • This pattern (Option<String> + jwt_secret_path + resolve_unattended) is intentionally only applied to ingestion-replication-service because it is the only listed service with a non-stub main.rs. The same wiring should be propagated to other services as their binaries are fleshed out.
  • Two unrelated pre-existing issues in services/ingestion-replication-service (duplicate fn main() in build.rs, duplicate use and unclosed delimiter in main.rs) prevent that crate from compiling at HEAD; the edits here are syntactically valid and do not depend on those being fixed first.

@unnamedlab unnamedlab marked this pull request as ready for review April 30, 2026 11:36
@unnamedlab unnamedlab merged commit 84dd261 into main Apr 30, 2026
unnamedlab pushed a commit that referenced this pull request May 18, 2026
…-5-runner

feat(pipeline-runner): execute pipelineplan.Plan with Iceberg providers (ADR-0045 Phase C.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants