Class: Aidp::Security::SecretsRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/security/secrets_registry.rb

Overview

Registry for user-declared secrets that should be proxied Secrets are registered by name, with the actual value stored securely and never exposed directly to agents.

Storage: .aidp/security/secrets_registry.json Format: { “SECRET_NAME”: { “env_var”: “ACTUAL_ENV_VAR”, “registered_at”: timestamp } }

The registry only stores metadata - actual secret values come from environment variables at runtime. This ensures secrets are never persisted to disk.

Constant Summary collapse

REGISTRY_FILENAME =
"secrets_registry.json"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_dir: Dir.pwd) ⇒ SecretsRegistry

Returns a new instance of SecretsRegistry.



24
25
26
27
28
29
# File 'lib/aidp/security/secrets_registry.rb', line 24

def initialize(project_dir: Dir.pwd)
  @project_dir = project_dir
  @cache = nil
  @cache_mtime = nil
  @mutex = Mutex.new
end

Instance Attribute Details

#project_dirObject (readonly)

Returns the value of attribute project_dir.



22
23
24
# File 'lib/aidp/security/secrets_registry.rb', line 22

def project_dir
  @project_dir
end

Instance Method Details

#clear_cache!Object

Clear the in-memory cache (forces reload on next access)



169
170
171
172
173
174
# File 'lib/aidp/security/secrets_registry.rb', line 169

def clear_cache!
  @mutex.synchronize do
    @cache = nil
    @cache_mtime = nil
  end
end

#env_var_for(name) ⇒ String?

Get the environment variable name for a secret

Parameters:

  • name (String)

    The secret name

Returns:

  • (String, nil)

    The env var name or nil if not registered



116
117
118
119
# File 'lib/aidp/security/secrets_registry.rb', line 116

def env_var_for(name)
  entry = get(name)
  entry&.dig(:env_var) || entry&.dig("env_var")
end

#env_var_registered?(env_var) ⇒ Boolean

Check if an environment variable is registered as a secret

Parameters:

  • env_var (String)

    The environment variable name

Returns:

  • (Boolean)


151
152
153
154
155
156
# File 'lib/aidp/security/secrets_registry.rb', line 151

def env_var_registered?(env_var)
  @mutex.synchronize do
    registry = load_registry
    registry.values.any? { |entry| (entry[:env_var] || entry["env_var"]) == env_var }
  end
end

#env_vars_to_stripArray<String>

Get list of environment variables that should be stripped from agent environment

Returns:

  • (Array<String>)

    List of env var names to remove



141
142
143
144
145
146
# File 'lib/aidp/security/secrets_registry.rb', line 141

def env_vars_to_strip
  @mutex.synchronize do
    registry = load_registry
    registry.values.map { |entry| entry[:env_var] || entry["env_var"] }.compact.uniq
  end
end

#get(name) ⇒ Hash?

Get registration details for a secret (without the actual value)

Parameters:

  • name (String)

    The secret name

Returns:

  • (Hash, nil)

    Registration details or nil if not found



103
104
105
106
107
108
109
110
111
# File 'lib/aidp/security/secrets_registry.rb', line 103

def get(name)
  @mutex.synchronize do
    registry = load_registry
    entry = registry[name.to_sym] || registry[name.to_s]
    return nil unless entry

    entry.merge(name: name)
  end
end

#listArray<Hash>

List all registered secrets (names and metadata only, never values)

Returns:

  • (Array<Hash>)

    List of registered secrets with metadata



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/aidp/security/secrets_registry.rb', line 123

def list
  @mutex.synchronize do
    registry = load_registry
    registry.map do |name, details|
      {
        name: name,
        env_var: details[:env_var] || details["env_var"],
        description: details[:description] || details["description"],
        scopes: details[:scopes] || details["scopes"] || [],
        registered_at: details[:registered_at] || details["registered_at"],
        has_value: ENV.key?(details[:env_var] || details["env_var"])
      }
    end
  end
end

#name_for_env_var(env_var) ⇒ String?

Get the secret name for an environment variable

Parameters:

  • env_var (String)

    The environment variable name

Returns:

  • (String, nil)

    The secret name or nil if not found



161
162
163
164
165
166
# File 'lib/aidp/security/secrets_registry.rb', line 161

def name_for_env_var(env_var)
  @mutex.synchronize do
    registry = load_registry
    registry.find { |_name, entry| (entry[:env_var] || entry["env_var"]) == env_var }&.first
  end
end

#register(name:, env_var:, description: nil, scopes: []) ⇒ Hash

Register a secret by name

Parameters:

  • name (String)

    The name to reference this secret by

  • env_var (String)

    The environment variable containing the secret

  • description (String) (defaults to: nil)

    Optional description of what this secret is for

  • scopes (Array<String>) (defaults to: [])

    Optional list of allowed operations for this secret

Returns:

  • (Hash)

    Registration details



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/aidp/security/secrets_registry.rb', line 37

def register(name:, env_var:, description: nil, scopes: [])
  @mutex.synchronize do
    registry = load_registry

    # Validate the environment variable exists (but don't store the value)
    unless ENV.key?(env_var)
      Aidp.log_warn("security.registry", "env_var_not_found",
        name: name,
        env_var: env_var)
    end

    registration = {
      env_var: env_var,
      description: description,
      scopes: scopes,
      registered_at: Time.now.iso8601,
      id: SecureRandom.hex(8)
    }

    registry[name] = registration
    save_registry(registry)

    Aidp.log_info("security.registry", "secret_registered",
      name: name,
      env_var: env_var,
      scopes: scopes)

    registration.merge(name: name)
  end
end

#registered?(name) ⇒ Boolean

Check if a secret is registered

Parameters:

  • name (String)

    The secret name

Returns:

  • (Boolean)


93
94
95
96
97
98
# File 'lib/aidp/security/secrets_registry.rb', line 93

def registered?(name)
  @mutex.synchronize do
    registry = load_registry
    registry.key?(name.to_sym) || registry.key?(name.to_s)
  end
end

#unregister(name:) ⇒ Boolean

Unregister a secret

Parameters:

  • name (String)

    The secret name to remove

Returns:

  • (Boolean)

    true if removed, false if not found



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/aidp/security/secrets_registry.rb', line 71

def unregister(name:)
  @mutex.synchronize do
    registry = load_registry

    key = registry.key?(name.to_sym) ? name.to_sym : name.to_s
    unless registry.key?(key)
      Aidp.log_warn("security.registry", "secret_not_found_for_unregister",
        name: name)
      return false
    end

    registry.delete(key)
    save_registry(registry)

    Aidp.log_info("security.registry", "secret_unregistered", name: name)
    true
  end
end