Module: RosettAi::SecretsResolver

Defined in:
lib/rosett_ai/secrets_resolver.rb

Overview

Resolves secrets through an ordered fallback chain. Resolution order: ENV variable > secrets file (0600). System keyring support planned for GUI phase (P3).

Constant Summary collapse

SAFE_PERMISSIONS =
0o600

Class Method Summary collapse

Class Method Details

.from_env(env_key) ⇒ String?

Reads a secret from the environment.

Parameters:

  • env_key (String)

    the environment variable name

Returns:

  • (String, nil)

    the value, or nil if unset or empty



28
29
30
31
# File 'lib/rosett_ai/secrets_resolver.rb', line 28

def from_env(env_key)
  value = ENV.fetch(env_key, nil)
  value unless value.nil? || value.empty?
end

.from_secrets_file(env_key) ⇒ String?

Reads a secret from the secrets YAML file.

Parameters:

  • env_key (String)

    the key to look up in the secrets file

Returns:

  • (String, nil)

    the value, or nil if the file is missing or key absent



37
38
39
40
41
42
43
44
45
46
# File 'lib/rosett_ai/secrets_resolver.rb', line 37

def from_secrets_file(env_key)
  secrets_path = RosettAi.paths.secrets_file.to_s
  return nil unless File.exist?(secrets_path)

  validate_permissions!(secrets_path)
  data = RosettAi::YamlLoader.load_file(secrets_path)
  return nil unless data.is_a?(Hash)

  data[env_key]
end

.raise_missing(env_key) ⇒ Object

Raises:



62
63
64
65
66
67
68
69
# File 'lib/rosett_ai/secrets_resolver.rb', line 62

def raise_missing(env_key)
  secrets_path = RosettAi.paths.secrets_file
  raise RosettAi::Error, <<~MSG.chomp
    #{env_key} not found. Set it via one of:
      1. export #{env_key}=<value>
      2. Add '#{env_key}: <value>' to #{secrets_path} (chmod 600)
  MSG
end

.resolve(env_key) ⇒ String

Resolves a secret through the fallback chain (ENV > secrets file).

Parameters:

  • env_key (String)

    the environment variable name to look up

Returns:

  • (String)

    the resolved secret value

Raises:



20
21
22
# File 'lib/rosett_ai/secrets_resolver.rb', line 20

def resolve(env_key)
  from_env(env_key) || from_secrets_file(env_key) || raise_missing(env_key)
end

.validate_permissions!(path)

This method returns an undefined value.

Validates that the file has strict 0600 permissions.

Parameters:

  • path (String)

    absolute path to the file

Raises:



53
54
55
56
57
58
59
60
# File 'lib/rosett_ai/secrets_resolver.rb', line 53

def validate_permissions!(path)
  mode = File.stat(path).mode & 0o777
  return if mode == SAFE_PERMISSIONS

  raise RosettAi::Error,
        "Secrets file #{path} has insecure permissions #{format('%04o', mode)}. " \
        "Expected 0600. Fix with: chmod 600 #{path}"
end