summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock7
-rw-r--r--components/crypto.rb47
-rw-r--r--components/provider.rb4
-rw-r--r--components/providers/openai.rb19
-rw-r--r--components/storage.rb33
-rw-r--r--controllers/cartridges.rb2
-rw-r--r--controllers/instance.rb8
-rw-r--r--controllers/session.rb12
-rw-r--r--nano-bots.gemspec1
-rw-r--r--ports/dsl/nano-bots.rb15
10 files changed, 122 insertions, 26 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index facc989..f5acf72 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,6 +7,7 @@ PATH
faraday (~> 2.7, >= 2.7.5)
pry (~> 0.14.2)
rainbow (~> 3.1, >= 3.1.1)
+ rbnacl (~> 7.1, >= 7.1.1)
ruby-openai (~> 4.0)
sweet-moon (~> 0.0.7)
@@ -35,6 +36,8 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
rainbow (3.1.1)
+ rbnacl (7.1.1)
+ ffi
regexp_parser (2.8.0)
rexml (3.2.5)
rspec (3.12.0)
@@ -50,7 +53,7 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-support (3.12.0)
- rubocop (1.51.0)
+ rubocop (1.52.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
@@ -60,7 +63,7 @@ GEM
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
- rubocop-ast (1.28.1)
+ rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.18.0)
rubocop (~> 1.41)
diff --git a/components/crypto.rb b/components/crypto.rb
new file mode 100644
index 0000000..3f97f57
--- /dev/null
+++ b/components/crypto.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'singleton'
+require 'rbnacl'
+require 'base64'
+
+module NanoBot
+ module Components
+ class Crypto
+ include Singleton
+
+ def initialize
+ password = ENV.fetch('NANO_BOTS_ENCRYPTION_PASSWORD', nil)
+
+ password = 'UNSAFE' unless password && password != ''
+
+ @box = RbNaCl::SecretBox.new(RbNaCl::Hash.sha256(password))
+ @fixed_nonce = RbNaCl::Hash.sha256(password)[0...@box.nonce_bytes]
+ end
+
+ def encrypt(content, soft: false)
+ return content unless @box
+
+ nonce = soft ? @fixed_nonce : RbNaCl::Random.random_bytes(@box.nonce_bytes)
+ Base64.urlsafe_encode64(nonce + @box.encrypt(nonce, content))
+ end
+
+ def decrypt(content)
+ return content unless @box
+
+ decoded_content = Base64.urlsafe_decode64(content)
+ nonce = decoded_content[0...@box.nonce_bytes]
+ cipher_text = decoded_content[@box.nonce_bytes..]
+
+ @box.decrypt(nonce, cipher_text)
+ end
+
+ def self.encrypt(content, soft: false)
+ instance.encrypt(content, soft:)
+ end
+
+ def self.decrypt(content)
+ instance.decrypt(content)
+ end
+ end
+ end
+end
diff --git a/components/provider.rb b/components/provider.rb
index dbfc8bd..3138cc4 100644
--- a/components/provider.rb
+++ b/components/provider.rb
@@ -7,10 +7,10 @@ require_relative './providers/openai'
module NanoBot
module Components
class Provider
- def self.new(provider)
+ def self.new(provider, environment: {})
case provider[:name]
when 'openai'
- Providers::OpenAI.new(provider[:settings])
+ Providers::OpenAI.new(provider[:settings], environment:)
else
raise "Unsupported provider #{provider[:name]}"
end
diff --git a/components/providers/openai.rb b/components/providers/openai.rb
index c0a6639..c64a588 100644
--- a/components/providers/openai.rb
+++ b/components/providers/openai.rb
@@ -3,6 +3,7 @@
require 'openai'
require_relative './base'
+require_relative '../crypto'
module NanoBot
module Components
@@ -15,8 +16,9 @@ module NanoBot
attr_reader :settings
- def initialize(settings)
+ def initialize(settings, environment: {})
@settings = settings
+ @environment = environment
@client = ::OpenAI::Client.new(
uri_base: "#{@settings[:credentials][:address].sub(%r{/$}, '')}/",
@@ -46,11 +48,16 @@ module NanoBot
)
end
- payload = {
- model: @settings[:model],
- user: @settings[:credentials][:'user-identifier'],
- messages:
- }
+ user = @settings[:credentials][:'user-identifier']
+
+ user_suffix = @environment && (
+ @environment['NANO_BOTS_USER_IDENTIFIER'] ||
+ @environment[:NANO_BOTS_USER_IDENTIFIER]
+ )
+
+ user = "#{user}/#{user_suffix}" if user_suffix && user_suffix != ''
+
+ payload = { model: @settings[:model], user: Crypto.encrypt(user, soft: true), messages: }
CHAT_SETTINGS.each do |key|
payload[key] = @settings[key] if @settings.key?(key)
diff --git a/components/storage.rb b/components/storage.rb
index cba3dd0..b6d8910 100644
--- a/components/storage.rb
+++ b/components/storage.rb
@@ -3,11 +3,12 @@
require 'babosa'
require_relative '../logic/helpers/hash'
+require_relative './crypto'
module NanoBot
module Components
class Storage
- def self.build_path_and_ensure_state_file!(key, cartridge)
+ def self.build_path_and_ensure_state_file!(key, cartridge, environment: {})
path = [
Logic::Helpers::Hash.fetch(cartridge, %i[state directory]),
ENV.fetch('NANO_BOTS_STATE_DIRECTORY', nil)
@@ -17,14 +18,38 @@ module NanoBot
path = "#{user_home!.sub(%r{/$}, '')}/.local/state/nano-bots" if path.nil?
- path = "#{path.sub(%r{/$}, '')}/ruby-nano-bots/#{cartridge[:meta][:author].to_slug.normalize}"
+ prefix = environment && (
+ environment['NANO_BOTS_USER_IDENTIFIER'] ||
+ environment[:NANO_BOTS_USER_IDENTIFIER]
+ )
+
+ path = "#{path.sub(%r{/$}, '')}/ruby-nano-bots/vault"
+
+ if prefix
+ normalized = prefix.split('/').map do |part|
+ Crypto.encrypt(
+ part.to_s.gsub('.', '-').force_encoding('UTF-8').to_slug.normalize,
+ soft: true
+ )
+ end.join('/')
+
+ path = "#{path}/#{normalized}"
+ end
+
+ path = "#{path}/#{cartridge[:meta][:author].to_slug.normalize}"
path = "#{path}/#{cartridge[:meta][:name].to_slug.normalize}"
- path = "#{path}/#{cartridge[:meta][:version].to_s.gsub('.', '-').to_slug.normalize}/#{key}"
+ path = "#{path}/#{cartridge[:meta][:version].to_s.gsub('.', '-').to_slug.normalize}"
+ path = "#{path}/#{Crypto.encrypt(key, soft: true)}"
path = "#{path}/state.json"
FileUtils.mkdir_p(File.dirname(path))
- File.write(path, JSON.generate({ key:, history: [] })) unless File.exist?(path)
+ unless File.exist?(path)
+ File.write(
+ path,
+ Crypto.encrypt(JSON.generate({ key:, history: [] }))
+ )
+ end
path
end
diff --git a/controllers/cartridges.rb b/controllers/cartridges.rb
index 151cc46..fe0d56e 100644
--- a/controllers/cartridges.rb
+++ b/controllers/cartridges.rb
@@ -39,7 +39,7 @@ module NanoBot
rescue StandardError => _e
end
- cartridges.sort_by { |cartridge| cartridge[:meta][:name] }
+ cartridges = cartridges.sort_by { |cartridge| cartridge[:meta][:name] }
cartridges.prepend(
{ system: { id: '-' }, meta: { name: 'Default', symbol: '🤖' } }
diff --git a/controllers/instance.rb b/controllers/instance.rb
index a982261..d4e0c1b 100644
--- a/controllers/instance.rb
+++ b/controllers/instance.rb
@@ -13,14 +13,16 @@ require_relative './session'
module NanoBot
module Controllers
class Instance
- def initialize(cartridge_path:, stream:, state: nil)
+ def initialize(cartridge_path:, stream:, state: nil, environment: {})
@stream = stream
load_cartridge!(cartridge_path)
- provider = Components::Provider.new(@cartridge[:provider])
+ provider = Components::Provider.new(@cartridge[:provider], environment:)
- @session = Session.new(provider:, cartridge: @cartridge, state:, stream: @stream)
+ @session = Session.new(
+ provider:, cartridge: @cartridge, state:, stream: @stream, environment:
+ )
end
def cartridge
diff --git a/controllers/session.rb b/controllers/session.rb
index 270b623..4694911 100644
--- a/controllers/session.rb
+++ b/controllers/session.rb
@@ -9,6 +9,7 @@ require_relative '../logic/cartridge/streaming'
require_relative '../logic/cartridge/interaction'
require_relative '../components/storage'
require_relative '../components/adapter'
+require_relative '../components/crypto'
module NanoBot
module Controllers
@@ -17,7 +18,7 @@ module NanoBot
class Session
attr_accessor :stream
- def initialize(provider:, cartridge:, state: nil, stream: $stdout)
+ def initialize(provider:, cartridge:, state: nil, stream: $stdout, environment: {})
@stream = stream
@provider = provider
@cartridge = cartridge
@@ -28,8 +29,9 @@ module NanoBot
@state = { history: [] }
else
@state_path = Components::Storage.build_path_and_ensure_state_file!(
- state.strip, @cartridge
+ state.strip, @cartridge, environment:
)
+
@state = load_state
end
end
@@ -39,11 +41,13 @@ module NanoBot
end
def load_state
- @state = Logic::Helpers::Hash.symbolize_keys(JSON.parse(File.read(@state_path)))
+ @state = Logic::Helpers::Hash.symbolize_keys(JSON.parse(
+ Components::Crypto.decrypt(File.read(@state_path))
+ ))
end
def store_state!
- File.write(@state_path, JSON.generate(@state))
+ File.write(@state_path, Components::Crypto.encrypt(JSON.generate(@state)))
end
def boot(mode:)
diff --git a/nano-bots.gemspec b/nano-bots.gemspec
index eb6f748..d66ec6c 100644
--- a/nano-bots.gemspec
+++ b/nano-bots.gemspec
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
spec.add_dependency 'faraday', '~> 2.7', '>= 2.7.5'
spec.add_dependency 'pry', '~> 0.14.2'
spec.add_dependency 'rainbow', '~> 3.1', '>= 3.1.1'
+ spec.add_dependency 'rbnacl', '~> 7.1', '>= 7.1.1'
spec.add_dependency 'ruby-openai', '~> 4.0'
spec.add_dependency 'sweet-moon', '~> 0.0.7'
diff --git a/ports/dsl/nano-bots.rb b/ports/dsl/nano-bots.rb
index 89da466..cbfe7f9 100644
--- a/ports/dsl/nano-bots.rb
+++ b/ports/dsl/nano-bots.rb
@@ -9,8 +9,13 @@ require_relative '../../controllers/interfaces/cli'
require_relative '../../components/stream'
module NanoBot
- def self.new(cartridge: '-', state: '-')
- Controllers::Instance.new(cartridge_path: cartridge, state:, stream: Components::Stream.new)
+ def self.new(cartridge: '-', state: '-', environment: {})
+ Controllers::Instance.new(
+ cartridge_path: cartridge,
+ state:,
+ stream: Components::Stream.new,
+ environment:
+ )
end
def self.cartridges
@@ -21,8 +26,10 @@ module NanoBot
Controllers::Interfaces::CLI.handle!
end
- def self.repl(cartridge: '-', state: '-')
- Controllers::Instance.new(cartridge_path: cartridge, state:, stream: $stdout).repl
+ def self.repl(cartridge: '-', state: '-', environment: {})
+ Controllers::Instance.new(
+ cartridge_path: cartridge, state:, stream: $stdout, environment:
+ ).repl
end
def self.version