- Changed all 50 org file :tangle targets from ../lisp/ to ~/.local/share/passepartout/lisp/ (XDG data dir) - Removed 49 generated .lisp files from project lisp/ directory - Removed tests/system-integration-tests.lisp (generated) - Removed lisp/*.fasl (compiled, stale) - Updated core-manifest.org to tangle .asd to XDG root - Remapped quicklisp symlink: local-projects/passepartout → XDG TUI fixes in channel-tui-main.org: - Removed with-raw-terminal (stty raw breaks fd 0 reads in this SBCL) - Use cat subprocess + pipe for keyboard input (via :input :interactive) - Blocking read-char on pipe with with-timeout 0.1s for daemon processing - Key events queued via drain-queue alongside daemon messages - Full dialog key routing (Escape, Up/Down, Enter, filters, Backspace) - SIGWINCH resize handling - Post-handshake backend-size re-query - Daemon version in status bar (was v0.5.0 hardcoded) - Handshake version stored in state, no add-msg - :daemon-version and :size-queried in state plist - view-status uses draw-rect for background - Test section gated with #+passepartout-tests
159 lines
5.4 KiB
Org Mode
159 lines
5.4 KiB
Org Mode
#+TITLE: SKILL: Credentials Vault (org-skill-credentials-vault.org)
|
|
#+AUTHOR: Agent
|
|
#+FILETAGS: :system:security:vault:
|
|
#+PROPERTY: header-args:lisp :tangle /home/user/.local/share/passepartout/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
|
|
|
|
* 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 |