Class: Aidp::Security::WorkLoopAdapter
- Inherits:
-
Object
- Object
- Aidp::Security::WorkLoopAdapter
- Defined in:
- lib/aidp/security/work_loop_adapter.rb
Overview
Adapts the Rule of Two security framework to the WorkLoopRunner Tracks trifecta state per work unit and enforces policy before agent calls
Integration points:
-
Work unit start/end lifecycle
-
Untrusted input detection (issues, PRs, external data)
-
Egress detection (git operations, API calls)
-
Private data detection (registered secrets)
Usage:
adapter = WorkLoopAdapter.new(project_dir: Dir.pwd)
adapter.begin_work_unit(work_unit_id: "unit_123", context: context)
adapter.check_agent_call_allowed!(operation: :git_push)
adapter.end_work_unit
Constant Summary collapse
- UNTRUSTED_SOURCES =
Sources of untrusted input that trigger the untrusted_input flag
%w[ github_issue github_pr github_comment external_url user_provided_url webhook_payload ].freeze
- EGRESS_OPERATIONS =
Operations that constitute egress (external communication)
%w[ git_push git_fetch api_call http_request webhook_send email_send file_upload pr_comment issue_comment ].freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#current_state ⇒ Object
readonly
Returns the value of attribute current_state.
-
#current_work_unit_id ⇒ Object
readonly
Returns the value of attribute current_work_unit_id.
-
#project_dir ⇒ Object
readonly
Returns the value of attribute project_dir.
Instance Method Summary collapse
-
#begin_work_unit(work_unit_id:, context: {}) ⇒ TrifectaState
Begin tracking a work unit.
-
#check_agent_call_allowed!(operation:, requires_credentials: false) ⇒ TrifectaState
Check if an agent call would be allowed and enable egress flag.
-
#enabled? ⇒ Boolean
Check if security enforcement is enabled.
-
#end_work_unit ⇒ Hash
End tracking for current work unit.
-
#initialize(project_dir:, config: nil, enforcer: nil, secrets_proxy: nil) ⇒ WorkLoopAdapter
constructor
A new instance of WorkLoopAdapter.
-
#request_credential(secret_name:, scope: nil) ⇒ Hash
Request credentials through the secrets proxy This enables the private_data flag and returns a short-lived token.
-
#sanitized_environment ⇒ Hash
Get a sanitized environment for agent execution Strips all registered secrets from the environment.
-
#status ⇒ Object
Get current security status for display.
-
#with_sanitized_environment { ... } ⇒ Object
Execute a block with sanitized environment.
-
#would_allow?(flag) ⇒ Hash
Check if current state would allow enabling a flag.
Constructor Details
#initialize(project_dir:, config: nil, enforcer: nil, secrets_proxy: nil) ⇒ WorkLoopAdapter
Returns a new instance of WorkLoopAdapter.
45 46 47 48 49 50 51 52 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 45 def initialize(project_dir:, config: nil, enforcer: nil, secrets_proxy: nil) @project_dir = project_dir @config = config || load_security_config @enforcer = enforcer || Aidp::Security.enforcer @secrets_proxy = secrets_proxy || Aidp::Security.secrets_proxy @current_work_unit_id = nil @current_state = nil end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
20 21 22 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 20 def config @config end |
#current_state ⇒ Object (readonly)
Returns the value of attribute current_state.
20 21 22 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 20 def current_state @current_state end |
#current_work_unit_id ⇒ Object (readonly)
Returns the value of attribute current_work_unit_id.
20 21 22 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 20 def current_work_unit_id @current_work_unit_id end |
#project_dir ⇒ Object (readonly)
Returns the value of attribute project_dir.
20 21 22 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 20 def project_dir @project_dir end |
Instance Method Details
#begin_work_unit(work_unit_id:, context: {}) ⇒ TrifectaState
Begin tracking a work unit
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 64 def begin_work_unit(work_unit_id:, context: {}) return nil unless enabled? @current_work_unit_id = work_unit_id @current_state = @enforcer.begin_work_unit(work_unit_id: work_unit_id) # Analyze context for untrusted input detect_and_enable_untrusted_input(context) Aidp.log_debug("security.adapter", "work_unit_started", work_unit_id: work_unit_id, initial_state: @current_state.to_h) @current_state end |
#check_agent_call_allowed!(operation:, requires_credentials: false) ⇒ TrifectaState
Check if an agent call would be allowed and enable egress flag
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 100 def check_agent_call_allowed!(operation:, requires_credentials: false) return @current_state unless enabled? && @current_state operation_str = operation.to_s # Check if this operation constitutes egress if egress_operation?(operation_str) begin @current_state.enable(:egress, source: "agent_operation:#{operation_str}") rescue PolicyViolation => e Aidp.log_warn("security.adapter", "egress_blocked", operation: operation_str, reason: e., current_state: @current_state.to_h) raise end end # If operation requires credentials, check if we can enable private_data if requires_credentials begin @current_state.enable(:private_data, source: "credential_access:#{operation_str}") rescue PolicyViolation => e Aidp.log_warn("security.adapter", "credential_access_blocked", operation: operation_str, reason: e., current_state: @current_state.to_h) raise end end @current_state end |
#enabled? ⇒ Boolean
Check if security enforcement is enabled
55 56 57 58 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 55 def enabled? rule_of_two_config = @config[:rule_of_two] || {} rule_of_two_config.fetch(:enabled, true) end |
#end_work_unit ⇒ Hash
End tracking for current work unit
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 82 def end_work_unit return nil unless enabled? && @current_work_unit_id summary = @enforcer.end_work_unit(@current_work_unit_id) @current_work_unit_id = nil @current_state = nil Aidp.log_debug("security.adapter", "work_unit_ended", summary: summary) summary end |
#request_credential(secret_name:, scope: nil) ⇒ Hash
Request credentials through the secrets proxy This enables the private_data flag and returns a short-lived token
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 140 def request_credential(secret_name:, scope: nil) unless enabled? # If security is disabled, return direct access (legacy mode) env_var = @secrets_proxy.registry.env_var_for(secret_name) return {token: ENV[env_var], direct_access: true} if env_var raise UnregisteredSecretError.new(secret_name: secret_name) end # Check if enabling private_data would violate Rule of Two if @current_state&.would_create_trifecta?(:private_data) raise PolicyViolation.new( flag: :private_data, source: "credential_request:#{secret_name}", current_state: @current_state.to_h, message: "Cannot access credentials for '#{secret_name}' - would create lethal trifecta" ) end # Enable private_data flag @current_state&.enable(:private_data, source: "secrets_proxy:#{secret_name}") # Request token from proxy @secrets_proxy.request_token(secret_name: secret_name, scope: scope) end |
#sanitized_environment ⇒ Hash
Get a sanitized environment for agent execution Strips all registered secrets from the environment
169 170 171 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 169 def sanitized_environment @secrets_proxy.sanitized_environment end |
#status ⇒ Object
Get current security status for display
203 204 205 206 207 208 209 210 211 212 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 203 def status return {enabled: false} unless enabled? { enabled: true, active_work_unit: @current_work_unit_id, state: @current_state&.to_h, status_string: @current_state&.status_string || "No active work unit" } end |
#with_sanitized_environment { ... } ⇒ Object
Execute a block with sanitized environment
176 177 178 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 176 def with_sanitized_environment(&block) @secrets_proxy.with_sanitized_environment(&block) end |
#would_allow?(flag) ⇒ Hash
Check if current state would allow enabling a flag
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/aidp/security/work_loop_adapter.rb', line 183 def would_allow?(flag) return {allowed: true, reason: "Security disabled"} unless enabled? return {allowed: true, reason: "No active work unit"} unless @current_state if @current_state.would_create_trifecta?(flag) { allowed: false, reason: "Would create lethal trifecta", current_state: @current_state.to_h } else { allowed: true, reason: "Operation allowed", enabled_count: @current_state.enabled_count } end end |