Class: RosettAi::Config::SecretResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/rosett_ai/config/secret_resolver.rb

Overview

Resolves $secret:backend:key references in configuration values.

Security constraints:

  • NO regex anywhere — deterministic string parsing only (ReDoS prevention)
  • Single-pass resolution — resolved values are never re-scanned
  • Fail loudly — raises on missing secrets, never returns nil
  • File backend: 0600 permissions, owned by Process.uid, 64 KiB cap
  • Path backend: rejects ".." traversal

Defined Under Namespace

Classes: SecretError

Constant Summary collapse

SECRET_PREFIX =
'${secret:'
SECRET_SUFFIX =
'}'
VALID_BACKENDS =
['env', 'file', 'path'].freeze
MAX_FILE_SIZE =

64 KiB

65_536

Instance Method Summary collapse

Instance Method Details

#resolve(value) ⇒ Object

Resolve a single secret reference string.

Returns the original value unchanged if it is not a secret reference.

Parameters:

  • value (Object)

    a string potentially containing a secret reference, or any other object

Returns:

  • (Object)

    the resolved secret value, or the original value if not a reference

Raises:

  • (SecretError)

    if the secret backend is unknown or the secret cannot be resolved



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rosett_ai/config/secret_resolver.rb', line 31

def resolve(value)
  return value unless value.is_a?(String)
  return value unless secret_reference?(value)

  inner = value[SECRET_PREFIX.length..-2]
  backend, key = inner.split(':', 2)

  validate_reference!(backend, key)

  case backend
  when 'env'  then resolve_env(key)
  when 'file' then resolve_file(key)
  when 'path' then resolve_path(key)
  end
end

#resolve_all(obj) ⇒ Object

Walk a nested structure and resolve all secret references.

Single-pass: resolved values are never re-scanned.

Parameters:

  • obj (Hash, Array, String, Object)

    nested structure to resolve

Returns:

  • (Object)

    structure with all secret references resolved



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

def resolve_all(obj)
  case obj
  when Hash  then obj.transform_values { |v| resolve_all(v) }
  when Array then obj.map { |v| resolve_all(v) }
  when String then resolve(obj)
  else obj
  end
end