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(...)
end
Security 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..1000000
4. 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 }
}
}
end
2. 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
end
3. 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
end
4. 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)
)
# ...
end
Database 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
end
Migration 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)
end
Estimated 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
Priority: MEDIUM
Category: Security
Breaking Change: YES (if enabled by default)
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 anywhereSecurity Risks
1. Unauthorized Data Access
2. Data Tampering
3. Resource Exhaustion
4. Privilege Escalation
Solution
Implement role-based access control (RBAC) for robot operations.
Proposed Architecture
1. Robot Roles
2. Authorization Module
3. Robot Registration with Roles
4. Apply Authorization to Operations
Database Schema Changes
Configuration Examples
Testing
Migration Path
Phase 1: Additive (v0.3.0)
Phase 2: Opt-in (v0.4.0)
Phase 3: Required (v1.0.0)
Alternative: Simple Owner-Only Access
For simpler use cases, just restrict to owner:
Estimated Effort
6 hours:
References
ARCHITECTURE_REVIEW.md(Security Specialist section)Priority: MEDIUM
Category: Security
Breaking Change: YES (if enabled by default)