Files
passepartout/org/security-vault.org
Amr Gharbeia 924bf8f479
Some checks failed
Deploy (Gitea) / deploy (push) Failing after 2s
passepartout: v0.5.0 hotfix 2 — daemon stable
- Restore (in-package :passepartout) to core-reason
- Move *VAULT-MEMORY* back to core-skills
- Fix ASDF and defstruct/defpackage ordering
- Increase daemon timeout to 120s
- Handshake: 0.5.0

Verified: daemon processes messages, TUI clean, gate trace works
2026-05-07 20:14:51 -04:00

167 lines
5.5 KiB
Org Mode

#+TITLE: SKILL: Credentials Vault (org-skill-credentials-vault.org)
#+AUTHOR: Agent
#+FILETAGS: :system:security:vault:
#+PROPERTY: header-args:lisp :tangle ../lisp/security-vault.lisp
* Overview
The *Credentials Vault* provides secure in-memory storage for sensitive API keys and session tokens.
* Architectural Intent
The Credentials Vault isolates secrets from the rest of the system in
a dedicated hash-table. It provides simple get/set primitives with
environment-variable fallback for known providers. This is the single
place where credentials enter the system — every provider skill routes
through here.
** Contract
1. (vault-set provider secret &key type): stores secret under
~(format nil "~a-~a" provider type)~ in ~*vault-memory*~.
2. (vault-get provider &key type): returns the stored secret, or falls
back to the appropriate environment variable for known providers
(~:openai~, ~:anthropic~, ~:openrouter~, ~:gemini~). Returns NIL
if neither exists.
3. (vault-get-secret provider): wrapper — calls ~vault-get~ with
~:type :secret~.
4. (vault-set-secret provider secret): wrapper — calls ~vault-set~
with ~:type :secret~.
5. Vault isolation: storing a secret for provider A does not affect
provider B's entry. Different ~:type~ values produce different keys.
** Boundaries
- Does NOT encrypt at rest — that is the session layer's responsibility.
- Does NOT validate key format — the provider skill does that.
- Does NOT rotate or expire keys — this is a simple store.
* Implementation
** Package Context
#+begin_src lisp
(in-package :passepartout)
#+end_src
** Vault Storage
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defvar *vault-memory* (make-hash-table :test 'equal)
"In-memory cache of sensitive credentials.")
#+end_src
** Secret Management
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun vault-get (provider &key (type :api-key))
"Retrieves a credential from the vault or environment."
(let* ((key (format nil "~a-~a" provider type))
(val (gethash key *vault-memory*)))
(if val
val
(let ((env-var (case provider
(:gemini "GEMINI_API_KEY")
(:openai "OPENAI_API_KEY")
(:anthropic "ANTHROPIC_API_KEY")
(:openrouter "OPENROUTER_API_KEY")
(otherwise nil))))
(when env-var (uiop:getenv env-var))))))
#+end_src
** vault-set
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun vault-set (provider secret &key (type :api-key))
"Stores a secret in the vault."
(let ((key (format nil "~a-~a" provider type)))
(setf (gethash key *vault-memory*) secret)))
#+end_src
** Secret Wrappers (gateway-messaging)
Thin wrappers that match the export names used by =gateway-messaging=.
Delegates to the existing =vault-get=/=vault-set= with ~:type :secret~.
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun vault-get-secret (provider)
"Retrieves a stored secret or token for a gateway provider."
(vault-get provider :type :secret))
#+end_src
** vault-set-secret
;; REPL-VERIFIED: 2026-05-03T13:00:00
#+begin_src lisp
(defun vault-set-secret (provider secret)
"Stores a secret or token for a gateway provider."
(vault-set provider secret :type :secret))
#+end_src
** Skill Registration
#+begin_src lisp
(defskill :passepartout-security-vault
:priority 600
:trigger (lambda (ctx) (declare (ignore ctx)) nil))
#+end_src
** Vault Memory (relocated from core-skills)
#+begin_src lisp
defvar *VAULT-MEMORY* (make-hash-table :test 'equal))
#+end_src
#+end_src
* Test Suite
#+begin_src lisp
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload :fiveam :silent t))
(defpackage :passepartout-security-vault-tests
(:use :cl :fiveam :passepartout)
(:export #:vault-suite))
(in-package :passepartout-security-vault-tests)
(def-suite vault-suite :description "Verification of the Credentials Vault")
(in-suite vault-suite)
(test test-vault-round-trip
"Contract 1: vault-set stores a value; vault-get retrieves it."
(let ((test-key :vault-test-round-trip)
(test-secret "secret-abc123"))
(vault-set test-key test-secret)
(is (string= test-secret (vault-get test-key)))
;; Clean up
(vault-set test-key nil)))
(test test-vault-missing-key
"Contract 2: vault-get returns NIL for an unset, unknown provider."
(is (null (vault-get :nonexistent-provider-xyz))))
(test test-vault-isolation
"Contract 5: storing for provider A does not affect provider B."
(vault-set :vault-prov-a "secret-a")
(vault-set :vault-prov-b "secret-b")
(is (string= "secret-a" (vault-get :vault-prov-a)))
(is (string= "secret-b" (vault-get :vault-prov-b)))
(vault-set :vault-prov-a nil)
(vault-set :vault-prov-b nil))
(test test-vault-secret-wrappers
"Contracts 3,4: vault-get-secret and vault-set-secret use :type :secret."
(let ((test-provider :vault-secret-test))
(vault-set-secret test-provider "my-token")
(is (string= "my-token" (vault-get-secret test-provider)))
;; Clean up
(vault-set-secret test-provider nil)))
(test test-vault-type-isolation
"Contract 5: different :type values produce different keys."
(vault-set :vault-type-test "key-value" :type :api-key)
(vault-set :vault-type-test "secret-value" :type :secret)
(is (string= "key-value" (vault-get :vault-type-test :type :api-key)))
(is (string= "secret-value" (vault-get :vault-type-test :type :secret)))
(vault-set :vault-type-test nil :type :api-key)
(vault-set :vault-type-test nil :type :secret))
#+end_src