-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Problem
All robots can access and modify all memory nodes without any authorization checks. There's no access control mechanism to restrict what robots can do.
Current Implementation
Location: lib/htm.rb - No authorization anywhere
def add_node(key, value, ...)
# No check: Can this robot add this type of node?
# No check: Is this robot authorized to use this system?
@long_term_memory.add(key: key, value: value, robot_id: @robot_id, ...)
end
def forget(key, confirm: :confirmed)
# No check: Can this robot delete nodes created by other robots?
@long_term_memory.delete(key)
end
def recall(timeframe:, topic:, ...)
# No check: Can this robot access these memories?
@long_term_memory.search(...)
endSecurity Risks
1. Unauthorized Data Access
# Malicious robot can read all memories
malicious_robot = HTM.new(robot_name: "Hacker")
all_secrets = malicious_robot.recall(
timeframe: "all time",
topic: "password OR secret OR api_key"
)2. Data Tampering
# Robot can delete other robots' memories
robot_a.add_node("decision_001", "Use PostgreSQL")
robot_b.forget("decision_001", confirm: :confirmed) # Deletes A's decision!3. Resource Exhaustion
# Robot can flood system with nodes
malicious_robot.add_node("spam_#{i}", "junk" * 10000) for i in 1..10000004. Privilege Escalation
# Regular robot stores admin-level decision
regular_robot.add_node("admin_override", "Grant full access", type: :decision)Solution
Implement role-based access control (RBAC) for robot operations.
Proposed Architecture
1. Robot Roles
module HTM
module Roles
GUEST = :guest # Read-only, limited search
USER = :user # Read/write own nodes
CONTRIBUTOR = :contributor # Read all, write own
ADMIN = :admin # Full access
SYSTEM = :system # Internal operations
end
# Permission matrix
PERMISSIONS = {
guest: {
add_node: false,
recall: { scope: :public_only },
retrieve: { scope: :public_only },
forget: false,
stats: false
},
user: {
add_node: { scope: :own },
recall: { scope: :own },
retrieve: { scope: :own },
forget: { scope: :own },
stats: false
},
contributor: {
add_node: { scope: :own },
recall: { scope: :all },
retrieve: { scope: :all },
forget: { scope: :own },
stats: { scope: :own }
},
admin: {
add_node: { scope: :all },
recall: { scope: :all },
retrieve: { scope: :all },
forget: { scope: :all },
stats: { scope: :all }
}
}
end2. Authorization Module
class HTM
module Authorization
def self.can?(robot, action, resource = nil)
role = robot.role
perms = PERMISSIONS[role][action]
return false if perms == false
return true if perms == true
# Scope-based authorization
case perms[:scope]
when :own
resource.nil? || resource[:robot_id] == robot.robot_id
when :public_only
resource.nil? || resource[:visibility] == 'public'
when :all
true
else
false
end
end
def self.authorize!(robot, action, resource = nil)
unless can?(robot, action, resource)
raise HTM::AuthorizationError,
"Robot '#{robot.robot_name}' (role: #{robot.role}) not authorized to #{action}"
end
end
end
end3. Robot Registration with Roles
class HTM
attr_reader :robot_id, :robot_name, :role
def initialize(
working_memory_size: 128_000,
robot_id: nil,
robot_name: nil,
robot_role: :user, # NEW: default role
api_key: nil, # NEW: authentication
**opts
)
@robot_id = robot_id || SecureRandom.uuid
@robot_name = robot_name || "robot_#{@robot_id[0..7]}"
# Authenticate and determine role
@role = authenticate_and_authorize(api_key, robot_role)
# ...
end
private
def authenticate_and_authorize(api_key, requested_role)
# Option 1: API key-based auth
if api_key
validated_role = validate_api_key(api_key)
return validated_role
end
# Option 2: Environment-based (development)
if ENV['HTM_ROBOT_ROLE']
return ENV['HTM_ROBOT_ROLE'].to_sym
end
# Default: user role
:user
end
def validate_api_key(api_key)
# Query database for API key
with_connection do |conn|
result = conn.exec_params(
"SELECT role FROM robot_api_keys WHERE api_key = $1 AND revoked = false",
[api_key]
)
raise HTM::AuthenticationError, "Invalid API key" if result.ntuples.zero?
result.first['role'].to_sym
end
end
end4. Apply Authorization to Operations
def add_node(key, value, type: nil, ...)
# Authorization check
Authorization.authorize!(self, :add_node)
# Proceed with operation
embedding = @embedding_service.embed(value)
# ...
end
def forget(key, confirm: :confirmed)
raise ArgumentError, "Must pass confirm: :confirmed" unless confirm == :confirmed
# Fetch node to check ownership
node = retrieve(key)
raise HTM::NotFoundError, "Node not found: #{key}" unless node
# Authorization check
Authorization.authorize!(self, :forget, node)
# Proceed with deletion
@long_term_memory.delete(key)
# ...
end
def recall(timeframe:, topic:, limit: 20, strategy: :vector)
# Authorization check
Authorization.authorize!(self, :recall)
# Apply scope filter based on role
nodes = @long_term_memory.search(
timeframe: timeframe,
query: topic,
limit: limit,
strategy: strategy,
filter_robot_id: (@role == :user ? @robot_id : nil)
)
# ...
endDatabase Schema Changes
-- Add role to robots table
ALTER TABLE robots ADD COLUMN role TEXT DEFAULT 'user';
-- API keys for authentication
CREATE TABLE robot_api_keys (
id BIGSERIAL PRIMARY KEY,
api_key TEXT UNIQUE NOT NULL,
robot_id TEXT REFERENCES robots(id),
role TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
revoked BOOLEAN DEFAULT false
);
-- Add visibility to nodes for public/private distinction
ALTER TABLE nodes ADD COLUMN visibility TEXT DEFAULT 'private';
CREATE INDEX idx_nodes_visibility ON nodes(visibility);Configuration Examples
# Guest robot (read-only)
guest = HTM.new(
robot_name: "PublicReader",
robot_role: :guest
)
# Regular user robot
user = HTM.new(
robot_name: "Assistant",
robot_role: :user,
api_key: ENV['HTM_API_KEY']
)
# Admin robot
admin = HTM.new(
robot_name: "SystemAdmin",
robot_role: :admin,
api_key: ENV['HTM_ADMIN_KEY']
)Testing
class TestAuthorization < Minitest::Test
def test_guest_cannot_add_nodes
guest = HTM.new(robot_name: "Guest", robot_role: :guest)
error = assert_raises(HTM::AuthorizationError) do
guest.add_node("key", "value")
end
assert_match /not authorized to add_node/, error.message
end
def test_user_cannot_delete_other_robot_nodes
user1 = HTM.new(robot_name: "User1", robot_role: :user)
user2 = HTM.new(robot_name: "User2", robot_role: :user)
user1.add_node("user1_key", "value")
assert_raises(HTM::AuthorizationError) do
user2.forget("user1_key", confirm: :confirmed)
end
end
def test_admin_can_delete_any_node
user = HTM.new(robot_name: "User", robot_role: :user)
admin = HTM.new(robot_name: "Admin", robot_role: :admin, api_key: admin_key)
user.add_node("user_key", "value")
# Admin can delete
assert_nothing_raised do
admin.forget("user_key", confirm: :confirmed)
end
end
def test_user_only_recalls_own_nodes
user1 = HTM.new(robot_name: "User1", robot_role: :user)
user2 = HTM.new(robot_name: "User2", robot_role: :user)
user1.add_node("user1_key", "user1 data")
user2.add_node("user2_key", "user2 data")
# User1 should only see own nodes
results = user1.recall(timeframe: "last week", topic: "data")
assert results.all? { |n| n['robot_id'] == user1.robot_id }
end
endMigration Path
Phase 1: Additive (v0.3.0)
- Add roles to robots table (default: 'user')
- Add authorization module (disabled by default)
- Add API key support
Phase 2: Opt-in (v0.4.0)
- Enable authorization via config flag
- Provide migration guide for existing deployments
Phase 3: Required (v1.0.0)
- Authorization enabled by default
- Remove bypass flag
Alternative: Simple Owner-Only Access
For simpler use cases, just restrict to owner:
def forget(key, confirm: :confirmed)
node = retrieve(key)
# Simple check: only owner can delete
unless node['robot_id'] == @robot_id
raise HTM::AuthorizationError,
"Cannot delete node owned by robot '#{node['robot_id']}'"
end
@long_term_memory.delete(key)
endEstimated Effort
6 hours:
- 2h: Design and implement RBAC system
- 1h: Database schema changes and migrations
- 2h: Apply authorization to all operations
- 1h: Testing and documentation
References
- Architecture Review:
ARCHITECTURE_REVIEW.md(Security Specialist section) - RBAC patterns: https://en.wikipedia.org/wiki/Role-based_access_control
Priority: MEDIUM
Category: Security
Breaking Change: YES (if enabled by default)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels