chore: auto-commit uncommitted changes

This commit is contained in:
James 2026-03-25 06:04:04 -04:00
parent 599ab722b5
commit 55699985ae
121 changed files with 2575 additions and 242 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Clovis — build pipeline
# Clavis — build pipeline
# FIPS 140-3: BoringCrypto via GOEXPERIMENT=boringcrypto
# Requires Go 1.24+ (verified: go1.24.0)
#
@ -11,12 +11,12 @@
GOEXPERIMENT := boringcrypto
export GOEXPERIMENT
VAULT_DIR := clovis-vault
CLI_DIR := clovis-cli
CRYPTO_DIR := clovis-crypto
VAULT_DIR := clavis-vault
CLI_DIR := clavis-cli
CRYPTO_DIR := clavis-crypto
VAULT_BIN := $(VAULT_DIR)/clavitor
CLI_BIN := $(CLI_DIR)/clovis-cli
CLI_BIN := $(CLI_DIR)/clavis-cli
VAULT_ENTRY := ./cmd/clavitor
@ -64,21 +64,21 @@ verify-fips-vault:
# --- process management ---
stop-vault:
@pkill -f './clavitor$$' 2>/dev/null || pkill -f 'clovis-vault/clavitor$$' 2>/dev/null || true
@pkill -f './clavitor$$' 2>/dev/null || pkill -f 'clavis-vault/clavitor$$' 2>/dev/null || true
@sleep 0.5
stop: stop-vault
restart-vault: stop-vault
cd $(VAULT_DIR) && set -a && . ./.env && set +a && nohup ./clavitor > /tmp/clovis-vault.log 2>&1 &
cd $(VAULT_DIR) && set -a && . ./.env && set +a && nohup ./clavitor > /tmp/clavis-vault.log 2>&1 &
@sleep 1
@ss -tlnp | grep -q ':1984' && echo "vault running on :1984 ✓" || { echo "vault failed to start ✗"; cat /tmp/clovis-vault.log; exit 1; }
@ss -tlnp | grep -q ':1984' && echo "vault running on :1984 ✓" || { echo "vault failed to start ✗"; cat /tmp/clavis-vault.log; exit 1; }
restart: restart-vault
status:
@echo "--- processes ---"
@ps aux | grep -E '(clavitor|clovis)' | grep -v grep || echo "nothing running"
@ps aux | grep -E '(clavitor|clavis)' | grep -v grep || echo "nothing running"
@echo "--- ports ---"
@ss -tlnp | grep -E ':1984' || echo "no vault port open"
@echo "--- fips ---"
@ -87,7 +87,7 @@ status:
# --- logs ---
logs-vault:
@tail -f /tmp/clovis-vault.log
@tail -f /tmp/clavis-vault.log
# --- clean ---

View File

@ -1,28 +1,28 @@
# Clovis
# Clavis
Secure vault platform with multi-client support.
## Architecture
**Clovis is the vault server.** Everything else is a client that talks to it.
**Clavis is the vault server.** Everything else is a client that talks to it.
## Structure
### Active Development
| Directory | Purpose | Status |
|-----------|---------|--------|
| `clovis-vault/` | Vault server with embedded UI (Go, FIPS 140-3) | **Active** |
| `clovis-crypto/` | JavaScript crypto layer | **Active** |
| `clovis-cli/` | CLI for agents | **Active** |
| `clovis-chrome/` | Chrome browser extension | **Active** |
| `clavis-vault/` | Vault server with embedded UI (Go, FIPS 140-3) | **Active** |
| `clavis-crypto/` | JavaScript crypto layer | **Active** |
| `clavis-cli/` | CLI for agents | **Active** |
| `clavis-chrome/` | Chrome browser extension | **Active** |
### Planned
| Directory | Purpose | Status |
|-----------|---------|--------|
| `clovis-firefox/` | Firefox browser extension | Announced |
| `clovis-safari/` | Safari browser extension | Announced |
| `clovis-ios/` | iOS native app | Announced |
| `clovis-android/` | Android native app | Announced |
| `clavis-firefox/` | Firefox browser extension | Announced |
| `clavis-safari/` | Safari browser extension | Announced |
| `clavis-ios/` | iOS native app | Announced |
| `clavis-android/` | Android native app | Announced |
## Build
@ -38,9 +38,9 @@ make logs-web # Tail web logs
## Clients
The vault supports multiple client types:
- **Web**: Built-in UI served by vault (`clovis-vault/`)
- **CLI**: Command-line tool for automation/agents (`clovis-cli/`)
- **Browser Extension**: Auto-fill and TOTP in Chrome (`clovis-chrome/`)
- **Web**: Built-in UI served by vault (`clavis-vault/`)
- **CLI**: Command-line tool for automation/agents (`clavis-cli/`)
- **Browser Extension**: Auto-fill and TOTP in Chrome (`clavis-chrome/`)
- **Mobile**: Native iOS/Android apps (planned)
## Security

View File

@ -0,0 +1,55 @@
# clavis-cli
Pure C CLI for credential access by AI agents. Talks to a Clavitor vault over HTTPS, decrypts L2 fields locally.
## Build
```
make # build for host
make strip # strip binary
make clean # remove artifacts
```
Target: `clavitor-cli` binary, <1MB stripped. Requires: C11 compiler, POSIX (Linux/macOS/FreeBSD/Windows).
## Architecture
- **src/main.c** — CLI entry point, argument parsing, commands (get, list, totp, test-crypto, test-roundtrip, eval, test-totp)
- **src/http.c** — HTTPS client using BearSSL. Loads system CA certs for TLS validation. Supports plain HTTP fallback.
- **src/keystore.c** — Config storage at `~/.config/clavitor/config`. AES-128-GCM encrypted + HMAC-SHA256 signed. Inconvenience barrier only — real security is vault-side.
- **src/jsbridge.c** — QuickJS bridge exposing BearSSL crypto primitives to JS. Loads `crypto/crypto.js` and `crypto/totp.js` from `../clavis-crypto/`.
- **src/util.c** — Base64 (standard + url-safe), URL encoding.
## Vendored dependencies
All in `vendor/`, no system package dependencies:
- **BearSSL** — TLS, AES-GCM, HKDF, HMAC, PRNG
- **QuickJS** — JS runtime for shared crypto logic
- **cJSON** — JSON parsing
## Crypto design
Three-tier encryption model:
- **L1** — first 8 bytes of L2 key, used as Bearer auth token
- **L2** — 16-byte AES-128-GCM key, client-side field encryption/decryption
- **L3** — requires hardware key (not handled by CLI)
JS crypto in `../clavis-crypto/` is the single source of truth for encrypt/decrypt logic. The C code bridges BearSSL primitives into QuickJS so the same JS runs in CLI and browser.
## Token format
`--token` value is a base64url-encoded, AES-GCM encrypted blob containing: `vault_host \0 agent_name \0 l2_key_16_bytes`. Decrypted using HKDF-derived key from seed `clavitor-l2-`.
## Vault communication
All API calls go to `https://<host>:1984` with `Authorization: Bearer <L1>` and `X-Agent: <agent_name>` headers.
Endpoints used: `/api/entries`, `/api/search?q=`, `/api/entries/<id>`, `/api/ext/totp/<id>`.
## Testing
```
./clavitor-cli test-crypto # BearSSL + JS crypto self-tests
./clavitor-cli test-totp <seed> # TOTP generation from base32 seed
./clavitor-cli test-roundtrip # runs crypto/test_crypto.js
```

View File

@ -32,7 +32,7 @@ VENDOR_DIR := vendor
BEARSSL_DIR := $(VENDOR_DIR)/bearssl
QUICKJS_DIR := $(VENDOR_DIR)/quickjs
CJSON_DIR := $(VENDOR_DIR)/cjson
CRYPTO_DIR := ../clovis-crypto
CRYPTO_DIR := ../clavis-crypto
# Output binary
BIN := clavitor-cli

View File

@ -329,7 +329,7 @@ static int parse_url(const char *url, struct parsed_url *out) {
/* --- parse HTTP response --- */
static int parse_response(char *resp_buf, size_t resp_len, struct v84_response *resp) {
static int parse_response(char *resp_buf, size_t resp_len, struct clv_response *resp) {
if (resp_len < 12 || strncmp(resp_buf, "HTTP/", 5) != 0) {
fprintf(stderr, "error: invalid HTTP response\n");
free(resp_buf);
@ -362,7 +362,7 @@ static int parse_response(char *resp_buf, size_t resp_len, struct v84_response *
/* --- HTTP GET (plain) --- */
static int http_get_plain(const struct parsed_url *pu, const char *bearer_token,
const char *agent_name, struct v84_response *resp) {
const char *agent_name, struct clv_response *resp) {
SOCKET fd = tcp_connect(pu->host, pu->port);
if (fd == INVALID_SOCKET) return -1;
@ -401,7 +401,7 @@ static int http_get_plain(const struct parsed_url *pu, const char *bearer_token,
/* --- HTTPS GET (BearSSL TLS) --- */
static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
const char *agent_name, struct v84_response *resp) {
const char *agent_name, struct clv_response *resp) {
SOCKET fd = tcp_connect(pu->host, pu->port);
if (fd == INVALID_SOCKET) return -1;
@ -476,7 +476,7 @@ static int http_get_tls(const struct parsed_url *pu, const char *bearer_token,
/* --- public API --- */
int http_get(const char *url, const char *bearer_token, const char *agent_name,
struct v84_response *resp) {
struct clv_response *resp) {
memset(resp, 0, sizeof(*resp));
struct parsed_url pu;

View File

@ -2,12 +2,12 @@
* clavitor CLI HTTPS client (BearSSL)
*/
#ifndef V84_HTTP_H
#define V84_HTTP_H
#ifndef CLV_HTTP_H
#define CLV_HTTP_H
#include <stddef.h>
struct v84_response {
struct clv_response {
int status; /* HTTP status code */
char *body; /* response body (malloc'd, caller frees) */
size_t body_len;
@ -16,6 +16,6 @@ struct v84_response {
/* Perform HTTPS GET with Bearer auth + optional X-Agent header.
* agent_name can be NULL to omit the header. */
int http_get(const char *url, const char *bearer_token, const char *agent_name,
struct v84_response *resp);
struct clv_response *resp);
#endif

View File

@ -73,7 +73,7 @@ static JSValue js_random_bytes(JSContext *ctx, JSValueConst this_val,
uint8_t *buf = malloc((size_t)n);
br_hmac_drbg_context drbg;
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "vault1984-seed", 14);
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "clavitor-seed", 13);
/* Seed with system RNG */
uint8_t seed[32];
@ -105,7 +105,7 @@ static JSValue js_aes_gcm_encrypt(JSContext *ctx, JSValueConst this_val,
/* Generate random 12-byte nonce */
uint8_t nonce[12];
br_hmac_drbg_context drbg;
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "vault1984-nonce", 15);
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "clavitor-nonce", 14);
br_prng_seeder seeder = br_prng_seeder_system(NULL);
if (seeder) seeder(&drbg.vtable);
br_hmac_drbg_generate(&drbg, nonce, 12);
@ -453,8 +453,8 @@ char *jsbridge_totp(const char *seed_b32) {
if (!ctx) return NULL;
JSValue global = JS_GetGlobalObject(ctx);
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
JSValue totp_obj = JS_GetPropertyStr(ctx, v84, "totp");
JSValue clv = JS_GetPropertyStr(ctx, global, "clavitor");
JSValue totp_obj = JS_GetPropertyStr(ctx, clv, "totp");
JSValue fn = JS_GetPropertyStr(ctx, totp_obj, "generate_totp");
JSValue args[1] = { JS_NewString(ctx, seed_b32) };
@ -479,7 +479,7 @@ char *jsbridge_totp(const char *seed_b32) {
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, fn);
JS_FreeValue(ctx, totp_obj);
JS_FreeValue(ctx, v84);
JS_FreeValue(ctx, clv);
JS_FreeValue(ctx, global);
return out;
}
@ -513,8 +513,8 @@ char *jsbridge_encrypt_field(const unsigned char *key, size_t key_len,
if (!ctx) return NULL;
JSValue global = JS_GetGlobalObject(ctx);
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
JSValue crypto_obj = JS_GetPropertyStr(ctx, v84, "crypto");
JSValue clv = JS_GetPropertyStr(ctx, global, "clavitor");
JSValue crypto_obj = JS_GetPropertyStr(ctx, clv, "crypto");
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "encrypt_field");
JSValue key_arr = js_new_uint8array(ctx, key, key_len);
@ -542,7 +542,7 @@ char *jsbridge_encrypt_field(const unsigned char *key, size_t key_len,
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, fn);
JS_FreeValue(ctx, crypto_obj);
JS_FreeValue(ctx, v84);
JS_FreeValue(ctx, clv);
JS_FreeValue(ctx, global);
return out;
}
@ -552,8 +552,8 @@ char *jsbridge_decrypt_field(const unsigned char *key, size_t key_len,
if (!ctx) return NULL;
JSValue global = JS_GetGlobalObject(ctx);
JSValue v84 = JS_GetPropertyStr(ctx, global, "vault1984");
JSValue crypto_obj = JS_GetPropertyStr(ctx, v84, "crypto");
JSValue clv = JS_GetPropertyStr(ctx, global, "clavitor");
JSValue crypto_obj = JS_GetPropertyStr(ctx, clv, "crypto");
JSValue fn = JS_GetPropertyStr(ctx, crypto_obj, "decrypt_field");
JSValue key_arr = js_new_uint8array(ctx, key, key_len);
@ -581,7 +581,7 @@ char *jsbridge_decrypt_field(const unsigned char *key, size_t key_len,
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, fn);
JS_FreeValue(ctx, crypto_obj);
JS_FreeValue(ctx, v84);
JS_FreeValue(ctx, clv);
JS_FreeValue(ctx, global);
return out;
}

View File

@ -3,8 +3,8 @@
* Exposes BearSSL crypto primitives to JavaScript.
*/
#ifndef V84_JSBRIDGE_H
#define V84_JSBRIDGE_H
#ifndef CLV_JSBRIDGE_H
#define CLV_JSBRIDGE_H
#include <stddef.h>

View File

@ -1,5 +1,5 @@
/*
* v1984 CLI config and key storage
* clavitor CLI config and key storage
*
* Config file format (binary):
* [4 bytes] magic "V19\x01"
@ -35,7 +35,7 @@
#endif
/* Config encryption key — derived from a string that also appears in the web UI */
static const char CONFIG_SEED[] = "vault1984-l2-";
static const char CONFIG_SEED[] = "clavitor-l2-";
static const unsigned char CONFIG_MAGIC[4] = { 'V', '1', '9', 0x01 };
/* Derive 16-byte config encryption key from seed */
@ -74,7 +74,7 @@ static int get_config_dir(char *buf, size_t len) {
fprintf(stderr, "error: cannot determine home directory\n");
return -1;
}
snprintf(buf, len, "%s/.config/v1984", home);
snprintf(buf, len, "%s/.config/clavitor", home);
return 0;
}
@ -127,7 +127,7 @@ int keystore_init(const char *vault_url, const char *agent_name,
/* Generate nonce */
unsigned char nonce[12];
br_hmac_drbg_context drbg;
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "v1984-init", 10);
br_hmac_drbg_init(&drbg, &br_sha256_vtable, "clavitor-init", 13);
br_prng_seeder seeder = br_prng_seeder_system(NULL);
if (seeder) seeder(&drbg.vtable);
br_hmac_drbg_generate(&drbg, nonce, 12);
@ -177,14 +177,14 @@ int keystore_init(const char *vault_url, const char *agent_name,
memset(enc_key, 0, 16);
memset(hmac_key, 0, 16);
fprintf(stderr, "v1984: initialized\n");
fprintf(stderr, "clavitor: initialized\n");
fprintf(stderr, " vault: %s\n", vault_url);
fprintf(stderr, " agent: %s\n", agent_name);
fprintf(stderr, " config: %s/config\n", dir);
return 0;
}
int keystore_load(struct v84_config *cfg) {
int keystore_load(struct clv_config *cfg) {
memset(cfg, 0, sizeof(*cfg));
char dir[512];
@ -194,7 +194,7 @@ int keystore_load(struct v84_config *cfg) {
snprintf(path, sizeof(path), "%s/config", dir);
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "error: not initialized. Run: v1984 init\n");
fprintf(stderr, "error: not initialized. Run: clavitor init\n");
return -1;
}

View File

@ -1,5 +1,5 @@
/*
* v1984 CLI config and key storage
* clavitor CLI config and key storage
*
* Config is encrypted with a static key (inconvenience barrier)
* and signed with HMAC (tamper detection). Not security theater
@ -7,22 +7,22 @@
* and lockout.
*/
#ifndef V84_KEYSTORE_H
#define V84_KEYSTORE_H
#ifndef CLV_KEYSTORE_H
#define CLV_KEYSTORE_H
#include <stddef.h>
struct v84_config {
char vault_url[512]; /* e.g. https://use.vault1984.com:1984 */
struct clv_config {
char vault_url[512]; /* e.g. https://use.clavitor.com:1984 */
char agent_name[128]; /* e.g. claude-code-forge */
unsigned char l2_key[16]; /* L2 encryption key (16 bytes) */
};
/* Initialize config: encrypt and write to ~/.config/v1984/config */
/* Initialize config: encrypt and write to ~/.config/clavitor/config */
int keystore_init(const char *vault_url, const char *agent_name,
const unsigned char *l2_key, size_t l2_key_len);
/* Load config: read, verify signature, decrypt */
int keystore_load(struct v84_config *cfg);
int keystore_load(struct clv_config *cfg);
#endif

View File

@ -38,11 +38,11 @@ static void usage(void) {
/* --- URL + auth helpers --- */
static void build_url(char *buf, size_t len, const struct v84_config *cfg, const char *path) {
static void build_url(char *buf, size_t len, const struct clv_config *cfg, const char *path) {
snprintf(buf, len, "%s%s", cfg->vault_url, path);
}
static void get_bearer(const struct v84_config *cfg, char *buf, size_t len) {
static void get_bearer(const struct clv_config *cfg, char *buf, size_t len) {
base64_encode(cfg->l2_key, 8, buf, len); /* L1 = first 8 bytes */
}
@ -118,8 +118,8 @@ static int cmd_test_crypto(void) {
{ /* L2 field roundtrip */
char *r = jsbridge_eval(
"var k = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
"var ct = vault1984.crypto.l2_encrypt_field(k, 'abcdef01', 'pw', 'secret');"
"var pt = vault1984.crypto.l2_decrypt_field(k, 'abcdef01', 'pw', ct);"
"var ct = clavitor.crypto.l2_encrypt_field(k, 'abcdef01', 'pw', 'secret');"
"var pt = clavitor.crypto.l2_decrypt_field(k, 'abcdef01', 'pw', ct);"
"'l2 roundtrip: ' + (pt === 'secret' ? 'OK' : 'FAIL');");
if (r) { fprintf(stderr, " [JS] %s\n", r); free(r); }
}
@ -150,7 +150,7 @@ static int cmd_test_crypto(void) {
/* TOTP RFC 6238 test vector */
char *code = jsbridge_eval(
"vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);");
"clavitor.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);");
if (!code || strcmp(code, "287082") != 0) {
fprintf(stderr, "FAIL: TOTP (got %s, expect 287082)\n", code ? code : "null");
free(code); jsbridge_cleanup(); return 1;
@ -184,7 +184,7 @@ int main(int argc, char **argv) {
jsbridge_cleanup();
return 1;
}
char *r = jsbridge_eval("globalThis._v1984_test_result");
char *r = jsbridge_eval("globalThis._clavitor_test_result");
if (r) { printf("%s\n", r); }
int ok = (r && strstr(r, "FAILED") == NULL);
free(r);
@ -241,7 +241,7 @@ int main(int argc, char **argv) {
char js_code[2048];
snprintf(js_code, sizeof(js_code),
"(function() {"
" var seed = native_encode_utf8('vault1984-l2-');"
" var seed = native_encode_utf8('clavitor-l2-');"
" var encKey = native_hkdf_sha256(seed, null, native_encode_utf8('token'), 16);"
" var ct = native_base64_decode('%s');"
" var pt = native_aes_gcm_decrypt_blob(encKey, ct);"
@ -288,7 +288,7 @@ int main(int argc, char **argv) {
}
/* Build config from token */
struct v84_config cfg;
struct clv_config cfg;
memset(&cfg, 0, sizeof(cfg));
snprintf(cfg.vault_url, sizeof(cfg.vault_url), "https://%s:1984", vault_host);
snprintf(cfg.agent_name, sizeof(cfg.agent_name), "%s", agent_name);
@ -314,7 +314,7 @@ int main(int argc, char **argv) {
build_url(url, sizeof(url), &cfg, "/api/entries");
}
struct v84_response resp;
struct clv_response resp;
if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: request failed\n"); return 1; }
if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
if (json_output) { printf("%s\n", resp.body); free(resp.body); return 0; }
@ -348,7 +348,7 @@ int main(int argc, char **argv) {
snprintf(path, sizeof(path), "/api/search?q=%s", encoded);
build_url(url, sizeof(url), &cfg, path);
struct v84_response resp;
struct clv_response resp;
if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: search failed\n"); return 1; }
if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }

View File

@ -2,8 +2,8 @@
* clavitor CLI utility functions
*/
#ifndef V84_UTIL_H
#define V84_UTIL_H
#ifndef CLV_UTIL_H
#define CLV_UTIL_H
#include <stddef.h>

View File

@ -290,7 +290,7 @@
}
/* Return result string (for QuickJS eval or browser display) */
globalThis._v1984_test_result = summary;
globalThis._clavitor_test_result = summary;
return;
}
tests[idx](function() { run(idx + 1); });
@ -299,10 +299,10 @@
run(0);
/* For sync environments (QuickJS), result is available immediately */
if (typeof globalThis._v1984_test_result !== 'undefined') {
if (typeof globalThis._clavitor_test_result !== 'undefined') {
/* Used by CLI eval */
}
})();
/* Return result for jsbridge_eval */
globalThis._v1984_test_result || 'RUNNING (async — check console)';
globalThis._clavitor_test_result || 'RUNNING (async — check console)';

View File

@ -0,0 +1,7 @@
# Clavis Firefox Extension
Browser extension for Firefox.
**Status:** Planned, not yet implemented.
This extension will share the core logic with clavis-chrome.

View File

@ -0,0 +1 @@
{"sessionId":"43d3cb50-0614-4ad7-aa41-58df69624a39","pid":221635,"acquiredAt":1774423023146}

View File

@ -0,0 +1,47 @@
GO := /usr/local/go/bin/go
BINARY := clavitor
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE := $(shell date -u +%Y%m%d-%H%M)
LDFLAGS := -s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildDate=$(DATE)
# Deploy targets
REMOTE := clavitor-hq
REMOTE_PATH := /opt/clavitor/bin
export GOFIPS140 := latest
# --- Build targets ---
.PHONY: build build-all linux-amd64 linux-arm64 release test clean deploy
build: linux-amd64
build-all: linux-amd64 linux-arm64
linux-amd64:
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
$(GO) build -ldflags "$(LDFLAGS)" -o $(BINARY)-linux-amd64 ./cmd/clavitor
linux-arm64:
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc \
$(GO) build -ldflags "$(LDFLAGS)" -o $(BINARY)-linux-arm64 ./cmd/clavitor
release:
./scripts/release.sh
test:
$(GO) test ./...
clean:
rm -f $(BINARY)-linux-amd64 $(BINARY)-linux-arm64
# --- Deploy ---
deploy: linux-amd64
scp $(BINARY)-linux-amd64 $(REMOTE):/tmp/$(BINARY)-new
ssh $(REMOTE) 'sudo systemctl stop clavitor && mv /tmp/$(BINARY)-new $(REMOTE_PATH)/$(BINARY) && chmod +x $(REMOTE_PATH)/$(BINARY) && sudo systemctl start clavitor'
@echo "Deployed. Verifying..."
@sleep 2
@ssh $(REMOTE) 'sudo systemctl is-active clavitor'

View File

@ -53,7 +53,7 @@ One root of trust: your hardware authenticator. One tap unlocks both Credential
## Quick start
```bash
go build -o clavitor ./cmd/vault1984/
go build -o clavitor ./cmd/clavitor/
./clavitor
# Open http://localhost:1984/app/
# Register a passkey → vault is ready
@ -73,7 +73,7 @@ Create a token in the web UI (Tokens page), then add to your MCP client config:
"clavitor": {
"url": "http://localhost:1984/mcp",
"headers": {
"Authorization": "Bearer v1984_..."
"Authorization": "Bearer clavitor_..."
}
}
}

View File

@ -13,7 +13,6 @@ import (
"log"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
@ -21,7 +20,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/johanj/vault1984/lib"
"github.com/johanj/clavitor/lib"
"github.com/pquerna/otp/totp"
)
@ -106,18 +105,8 @@ func (h *Handlers) VaultInfo(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, http.StatusOK, map[string]string{"vault_id": vaultID})
}
// Version is derived from the binary's modification time.
var Version = func() string {
exe, err := os.Executable()
if err != nil {
return "dev"
}
info, err := os.Stat(exe)
if err != nil {
return "dev"
}
return info.ModTime().UTC().Format("20060102-1504")
}()
// Version is set by main via ldflags at build time.
var Version = "dev"
// Health returns server status.
func (h *Handlers) Health(w http.ResponseWriter, r *http.Request) {
@ -221,7 +210,7 @@ func (h *Handlers) AuthRegisterBegin(w http.ResponseWriter, r *http.Request) {
"challenge": challengeBytes,
"rp": map[string]string{"name": "Clavitor", "id": rpID(r)},
"user": map[string]any{
"id": []byte("vault1984-owner"),
"id": []byte("clavitor-owner"),
"name": "vault-owner",
"displayName": "Vault Owner",
},
@ -272,14 +261,14 @@ func (h *Handlers) AuthRegisterComplete(w http.ResponseWriter, r *http.Request)
// First passkey → create DB
if db == nil && len(req.PublicKey) > 0 {
// DB named from L1 key: vault1984-XXXXXX (base64url of first 4 bytes, no extension)
// DB named from L1 key: clavitor-XXXXXX (base64url of first 4 bytes, no extension)
var dbName string
if len(req.L1Key) >= 4 {
dbName = "vault1984-" + base64UrlEncode(req.L1Key[:4])
dbName = "clavitor-" + base64UrlEncode(req.L1Key[:4])
} else {
// Fallback: derive from public key hash (legacy compat)
// Fallback: derive from public key hash
hash := sha256.Sum256(req.PublicKey)
dbName = "vault1984-" + base64UrlEncode(hash[:4])
dbName = "clavitor-" + base64UrlEncode(hash[:4])
}
dbPath := filepath.Join(h.Cfg.DataDir, dbName)
newDB, err := lib.OpenDB(dbPath)
@ -1699,7 +1688,7 @@ func (h *Handlers) HandleWebAuthnRegisterBegin(w http.ResponseWriter, r *http.Re
"challenge": challenge,
"rp": map[string]string{"name": "Clavitor", "id": rpID(r)},
"user": map[string]any{
"id": []byte("vault1984-owner"),
"id": []byte("clavitor-owner"),
"name": "vault-owner",
"displayName": "Clavitor Owner",
},

View File

@ -19,7 +19,7 @@ import (
"testing"
"embed"
"github.com/johanj/vault1984/lib"
"github.com/johanj/clavitor/lib"
)
// --- test helpers ---

View File

@ -13,7 +13,7 @@ import (
"sync"
"time"
"github.com/johanj/vault1984/lib"
"github.com/johanj/clavitor/lib"
)
// base64Decode handles both standard and url-safe base64 (with or without padding).
@ -93,7 +93,7 @@ func VaultIDFromContext(ctx context.Context) int64 {
// Fully stateless: L1 arrives with every request, is used, then forgotten.
// No sessions, no stored keys. The server has zero keys of its own.
//
// Self-hosted mode: finds vault DB by globbing vault1984-* files.
// Self-hosted mode: finds vault DB by globbing clavitor-* files.
// Hosted mode: finds vault DB by base64url(L1[0:4]) → filename.
func L1Middleware(dataDir string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
@ -103,7 +103,7 @@ func L1Middleware(dataDir string) func(http.Handler) http.Handler {
// No auth = unauthenticated request (registration, login begin, etc.)
if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
// Try to open vault DB without L1 (for unauthenticated endpoints)
matches, _ := filepath.Glob(filepath.Join(dataDir, "vault1984-*"))
matches, _ := filepath.Glob(filepath.Join(dataDir, "clavitor-*"))
if len(matches) > 0 {
db, err := lib.OpenDB(matches[0])
if err == nil {
@ -141,7 +141,7 @@ func L1Middleware(dataDir string) func(http.Handler) http.Handler {
// Find vault DB by first 4 bytes of L1
vaultPrefix := base64UrlEncode(l1Raw[:4])
dbPath := filepath.Join(dataDir, "vault1984-"+vaultPrefix)
dbPath := filepath.Join(dataDir, "clavitor-"+vaultPrefix)
log.Printf("L1 auth: l1_hex=%x prefix=%s path=%s", l1Raw, vaultPrefix, dbPath)
var db *lib.DB

View File

@ -8,7 +8,7 @@ import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/johanj/vault1984/lib"
"github.com/johanj/clavitor/lib"
)
// NewRouter creates the main router with all routes registered.
@ -93,7 +93,7 @@ func NewRouter(cfg *lib.Config, webFS embed.FS, templateFS embed.FS) *chi.Mux {
w.Write(data)
}
}
r.Get("/vault1984.css", serveEmbedded("vault1984.css", "text/css; charset=utf-8"))
r.Get("/clavitor.css", serveEmbedded("clavitor.css", "text/css; charset=utf-8"))
r.Get("/worldmap.svg", serveEmbedded("worldmap.svg", "image/svg+xml"))
r.Get("/favicon.svg", serveEmbedded("favicon.svg", "image/svg+xml"))

View File

@ -6,7 +6,7 @@ import (
"strings"
"testing"
"github.com/johanj/vault1984/lib"
"github.com/johanj/clavitor/lib"
)
/*
@ -261,8 +261,8 @@ func TestTierIsolationDB(t *testing.T) {
func TestCLICrypto(t *testing.T) {
// Find CLI binary via absolute path
home := os.Getenv("HOME")
cliBin := home + "/dev/clavitor/clovis/clovis-cli/clovis-cli"
cliDir := home + "/dev/clavitor/clovis/clovis-cli"
cliBin := home + "/dev/clavitor/clavis/clavis-cli/clavis-cli"
cliDir := home + "/dev/clavitor/clavis/clavis-cli"
if _, err := os.Stat(cliBin); err != nil {
t.Skip("clavitor-cli not found — run 'make cli' first")
}

Binary file not shown.

View File

@ -8,8 +8,8 @@ import (
"os"
"strconv"
"github.com/johanj/vault1984/api"
"github.com/johanj/vault1984/lib"
"github.com/johanj/clavitor/api"
"github.com/johanj/clavitor/lib"
)
//go:embed web
@ -18,7 +18,15 @@ var webFS embed.FS
//go:embed templates
var templateFS embed.FS
// Set via -ldflags at build time.
var (
version = "dev"
commit = "unknown"
buildDate = "unknown"
)
func main() {
api.Version = version + " (" + commit + " " + buildDate + ")"
// Telemetry flags (all optional — without them, no telemetry runs).
telemetryFreq := flag.Int("telemetry-freq", envInt("TELEMETRY_FREQ", 0), "Telemetry POST interval in seconds (0 = disabled)")
telemetryHost := flag.String("telemetry-host", envStr("TELEMETRY_HOST", ""), "Telemetry endpoint URL")
@ -37,6 +45,7 @@ func main() {
Token: *telemetryToken,
DataDir: cfg.DataDir,
Mode: cfg.Mode,
Version: version,
})
// Start automatic backup scheduler (3 weekly + 3 monthly, rotated)

View File

@ -50,7 +50,7 @@
<h3 class="mb-4">2. Connect Claude Code</h3>
<p class="mb-4">In your terminal, run:</p>
<div class="code-block">claude mcp add clavitor --transport http --url http://localhost:1984/mcp \
--header "Authorization: Bearer v1984_your_token_here"</div>
--header "Authorization: Bearer clavitor_your_token_here"</div>
<p class="mt-4" style="font-size:0.8125rem;color:var(--muted)">That&rsquo;s it. Claude Code picks up the new server automatically.</p>
</div>
@ -60,7 +60,7 @@
<div class="code-block mb-4"><pre>Name: clavitor
URL: http://localhost:1984/mcp</pre></div>
<p class="mb-4">Click <strong>Add</strong>, then expand the entry and add a header:</p>
<div class="code-block"><pre>Authorization: Bearer v1984_your_token_here</pre></div>
<div class="code-block"><pre>Authorization: Bearer clavitor_your_token_here</pre></div>
</div>
<div class="card mb-6" style="border-color:var(--border-gold)">

View File

@ -49,14 +49,14 @@
url = "http://localhost:1984/mcp"
[mcp_servers.clavitor.headers]
Authorization = "Bearer v1984_your_token_here"</pre></div>
Authorization = "Bearer clavitor_your_token_here"</pre></div>
</div>
<div class="card mb-6">
<span class="badge mb-4">Option B</span>
<h3 class="mb-4">REST API + Function Calling</h3>
<p class="mb-4">Define <span class="vaultname">clav<span class="n">itor</span></span> endpoints as functions. Works with any LLM that supports function calling.</p>
<div class="code-block"><pre>curl http://localhost:1984/api/search?q=github \
-H "Authorization: Bearer v1984_your_token_here"
-H "Authorization: Bearer clavitor_your_token_here"
# Returns entries with credentials, URLs, TOTP codes
# Personal fields return: {"value":"[REDACTED]","l2":true}</pre></div>
@ -82,7 +82,7 @@ GET /api/ext/totp/{id} # get live TOTP code
GET /api/generate?length=32 # generate random password</pre></div>
</div>
<p style="font-size:0.875rem;color:var(--muted)">All endpoints require <code>Authorization: Bearer v1984_...</code></p>
<p style="font-size:0.875rem;color:var(--muted)">All endpoints require <code>Authorization: Bearer clavitor_...</code></p>
</div>
<div class="section container">

View File

@ -48,7 +48,7 @@
<h3 class="mb-4">2. 配置令牌</h3>
<p class="mb-4"><span class="vaultname">clav<span class="n">itor</span></span> 网页界面创建令牌,然后配置:</p>
<div class="code-block"><pre>claw config set clavitor.url "http://localhost:1984/mcp"
claw config set clavitor.token "v1984_your_token_here"</pre></div>
claw config set clavitor.token "clavitor_your_token_here"</pre></div>
</div>
<div class="card mb-6">

View File

@ -48,7 +48,7 @@
<h3 class="mb-4">2. Configure your token</h3>
<p class="mb-4">Create a token in the <span class="vaultname">clav<span class="n">itor</span></span> web UI, then set it in your OpenClaw config:</p>
<div class="code-block"><pre>claw config set clavitor.url "http://localhost:1984/mcp"
claw config set clavitor.token "v1984_your_token_here"</pre></div>
claw config set clavitor.token "clavitor_your_token_here"</pre></div>
</div>
<div class="card mb-6">

View File

@ -147,7 +147,7 @@ function normalize_key(key) {
}
function encrypt_field(key, field_label, plaintext) {
var info_str = 'vault1984-field-' + field_label;
var info_str = 'clavitor-field-' + field_label;
var nkey = normalize_key(key);
var aes_len = nkey.length; /* 16 or 32 */
@ -177,7 +177,7 @@ function encrypt_field(key, field_label, plaintext) {
* @returns {string|Promise<string>} plaintext
*/
function decrypt_field(key, field_label, ciphertext_b64) {
var info_str = 'vault1984-field-' + field_label;
var info_str = 'clavitor-field-' + field_label;
var nkey = normalize_key(key);
var aes_len = nkey.length;
@ -205,8 +205,8 @@ function l2_encrypt_field(key, entry_id, label, pt) { return encrypt_field(key,
function l2_decrypt_field(key, entry_id, label, ct) { return decrypt_field(key, label, ct); }
/* Export for both environments */
if (typeof globalThis.vault1984 === 'undefined') globalThis.vault1984 = {};
globalThis.vault1984.crypto = {
if (typeof globalThis.clavitor === 'undefined') globalThis.clavitor = {};
globalThis.clavitor.crypto = {
aes_gcm_encrypt: aes_gcm_encrypt,
aes_gcm_decrypt: aes_gcm_decrypt,
hkdf_sha256: hkdf_sha256,

View File

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 355 B

View File

@ -31,7 +31,7 @@
var BUILD = '20260321-stateless';
console.log('clavitor build: ' + BUILD);
// Stateless auth: no token variable. Auth state = sessionStorage has v1984_master.
// Stateless auth: no token variable. Auth state = sessionStorage has clavitor_master.
var entries = [];
var currentEntry = null;
@ -60,7 +60,7 @@
async function init() {
// Stateless: either we have the master key from a PRF tap, or we don't.
if (sessionStorage.getItem('v1984_master')) {
if (sessionStorage.getItem('clavitor_master')) {
restoreAppLayout();
loadEntries(true);
return;
@ -87,7 +87,7 @@
// isAdditional: true when adding another passkey after first registration
function showDevicePicker(isAdditional) {
var heading = isAdditional ? 'Add another passkey' : 'Welcome to <span class="vaultname">vault<span class="n">1984</span></span>';
var heading = isAdditional ? 'Add another passkey' : 'Welcome to <span class="vaultname">clav<span class="n">itor</span></span>';
var sub = isAdditional
? 'Register a backup device. You can always add more later from Security settings.'
: 'Your personal password vault. No accounts, no cloud. Just you and your passkey.';
@ -334,7 +334,7 @@
document.getElementById('app').innerHTML =
'<div class="unlock-wrap">' +
'<div class="unlock-inner">' +
'<div class="vaultname" style="font-size:2rem;margin-bottom:1rem">vault<span class="n">1984</span></div>' +
'<div class="vaultname" style="font-size:2rem;margin-bottom:1rem">clav<span class="n">itor</span></div>' +
'<h2 class="mb-2">Unlock Vault</h2>' +
'<p class="mb-8">Authenticate with your passkey to access your vault.</p>' +
'<div id="unlockError" class="alert alert-error hidden mb-4"></div>' +
@ -402,7 +402,7 @@
await ClavitorWebAuthn.storeMasterKey(extResults.prf.results.first);
}
if (!sessionStorage.getItem('v1984_master')) {
if (!sessionStorage.getItem('clavitor_master')) {
throw new Error('PRF output not available — cannot derive encryption keys');
}
console.log('clavitor: L1 bearer =', getL1Bearer());
@ -466,7 +466,7 @@
}
function lockVault() {
sessionStorage.removeItem('v1984_master');
sessionStorage.removeItem('clavitor_master');
showUnlock();
}
@ -482,7 +482,7 @@
} catch (e) {
if (e.message !== 'Unauthorized') {
// Vault not found (deleted/new device) — clear stale session
sessionStorage.removeItem('v1984_master');
sessionStorage.removeItem('clavitor_master');
init();
}
}
@ -1781,7 +1781,7 @@
try {
var info = await fetch('/health').then(function(r) { return r.json(); });
var html = '<div class="modal-body" style="text-align:center">' +
'<div class="vaultname" style="font-size:2.5rem;margin-bottom:0.5rem">vault<span class="n">1984</span></div>' +
'<div class="vaultname" style="font-size:2.5rem;margin-bottom:0.5rem">clav<span class="n">itor</span></div>' +
'<p class="text-subtle mb-6">Password manager for humans with AI assistants</p>' +
'<div style="display:inline-block;text-align:left;font-size:0.8125rem;line-height:2">' +
'<div><span class="text-subtle">Version</span> <strong>' + escapeHtml(info.version) + '</strong></div>' +

View File

@ -55,9 +55,9 @@
/* --- Test 1: L1 (8-byte) roundtrip --- */
tests.push(function(done) {
var name = 'L1 (8B) encrypt/decrypt roundtrip';
resolve(vault1984.crypto.encrypt_field(K8, 'username', 'johanj'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K8, 'username', 'johanj'), function(ct, err) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.crypto.decrypt_field(K8, 'username', ct), function(pt, err2) {
resolve(clavitor.crypto.decrypt_field(K8, 'username', ct), function(pt, err2) {
if (err2) fail(name, err2.message);
else if (pt === 'johanj') pass(name);
else fail(name, 'got "' + pt + '"');
@ -69,9 +69,9 @@
/* --- Test 2: L2 (16-byte) roundtrip --- */
tests.push(function(done) {
var name = 'L2 (16B) encrypt/decrypt roundtrip';
resolve(vault1984.crypto.encrypt_field(K16, 'password', 's3cret!P@ss'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'password', 's3cret!P@ss'), function(ct, err) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.crypto.decrypt_field(K16, 'password', ct), function(pt, err2) {
resolve(clavitor.crypto.decrypt_field(K16, 'password', ct), function(pt, err2) {
if (err2) fail(name, err2.message);
else if (pt === 's3cret!P@ss') pass(name);
else fail(name, 'got "' + pt + '"');
@ -83,9 +83,9 @@
/* --- Test 3: L3 (32-byte) roundtrip --- */
tests.push(function(done) {
var name = 'L3 (32B) encrypt/decrypt roundtrip';
resolve(vault1984.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.crypto.decrypt_field(K32, 'passport', ct), function(pt, err2) {
resolve(clavitor.crypto.decrypt_field(K32, 'passport', ct), function(pt, err2) {
if (err2) fail(name, err2.message);
else if (pt === 'NL12345678') pass(name);
else fail(name, 'got "' + pt + '"');
@ -97,9 +97,9 @@
/* --- Test 4: L2 key cannot decrypt L3 ciphertext --- */
tests.push(function(done) {
var name = 'L2 key rejects L3 ciphertext';
resolve(vault1984.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K32, 'passport', 'NL12345678'), function(ct, err) {
if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'passport', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K16, 'passport', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 key decrypted L3 data to "' + pt + '"');
done();
@ -110,9 +110,9 @@
/* --- Test 5: L3 key cannot decrypt L2 ciphertext --- */
tests.push(function(done) {
var name = 'L3 key rejects L2 ciphertext';
resolve(vault1984.crypto.encrypt_field(K16, 'password', 'secret'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'password', 'secret'), function(ct, err) {
if (err) { fail(name, 'encrypt failed: ' + err.message); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K32, 'password', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K32, 'password', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L3 key decrypted L2 data to "' + pt + '"');
done();
@ -123,9 +123,9 @@
/* --- Test 6: Wrong key rejection --- */
tests.push(function(done) {
var name = 'wrong key rejected';
resolve(vault1984.crypto.encrypt_field(K16, 'secret', 'value'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'secret', 'value'), function(ct, err) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(WRONG_K16, 'secret', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(WRONG_K16, 'secret', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'wrong key decrypted to "' + pt + '"');
done();
@ -136,9 +136,9 @@
/* --- Test 7: Wrong label rejection --- */
tests.push(function(done) {
var name = 'wrong label rejected';
resolve(vault1984.crypto.encrypt_field(K16, 'labelA', 'value'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'labelA', 'value'), function(ct, err) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'labelB', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K16, 'labelB', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'wrong label decrypted to "' + pt + '"');
done();
@ -149,9 +149,9 @@
/* --- Test 8: Empty string roundtrip --- */
tests.push(function(done) {
var name = 'empty string roundtrip';
resolve(vault1984.crypto.encrypt_field(K16, 'empty', ''), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'empty', ''), function(ct, err) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.crypto.decrypt_field(K16, 'empty', ct), function(pt, err2) {
resolve(clavitor.crypto.decrypt_field(K16, 'empty', ct), function(pt, err2) {
if (err2) fail(name, err2.message);
else if (pt === '') pass(name);
else fail(name, 'got "' + pt + '"');
@ -164,9 +164,9 @@
tests.push(function(done) {
var name = 'unicode roundtrip';
var unicode = 'pässwörd 密码 🔑';
resolve(vault1984.crypto.encrypt_field(K16, 'intl', unicode), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'intl', unicode), function(ct, err) {
if (err) { fail(name, err.message); done(); return; }
resolve(vault1984.crypto.decrypt_field(K16, 'intl', ct), function(pt, err2) {
resolve(clavitor.crypto.decrypt_field(K16, 'intl', ct), function(pt, err2) {
if (err2) fail(name, err2.message);
else if (pt === unicode) pass(name);
else fail(name, 'mismatch');
@ -182,9 +182,9 @@
* K16 = [11,22,33,44,55,66,77,88,99,110,111,112,113,114,115,116]
* These are different keys after normalization, so HKDF produces different field keys.
* L1 ciphertext must NOT be decryptable with L2 key. */
resolve(vault1984.crypto.encrypt_field(K8, 'field', 'test'), function(ct1, err) {
resolve(clavitor.crypto.encrypt_field(K8, 'field', 'test'), function(ct1, err) {
if (err) { fail(name, 'L1 encrypt: ' + err.message); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'field', ct1); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K16, 'field', ct1); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 key decrypted L1 data');
done();
@ -195,7 +195,7 @@
/* --- Test 11: TOTP generation (RFC 6238 test vector) --- */
tests.push(function(done) {
var name = 'TOTP RFC 6238 test vector';
var result = vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);
var result = clavitor.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);
resolve(result, function(code, err) {
if (err) fail(name, err.message);
else if (code === '287082') pass(name);
@ -207,7 +207,7 @@
/* --- Test 12: TOTP second test vector (time=1111111109) --- */
tests.push(function(done) {
var name = 'TOTP RFC 6238 test vector #2';
var result = vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 1111111109, 30, 8);
var result = clavitor.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 1111111109, 30, 8);
resolve(result, function(code, err) {
if (err) fail(name, err.message);
else if (code === '07081804') pass(name);
@ -220,10 +220,10 @@
tests.push(function(done) {
var name = 'L3 TOTP seed inaccessible to agent (L2 key)';
var seed = 'JBSWY3DPEHPK3PXP'; // a TOTP seed
resolve(vault1984.crypto.encrypt_field(K32, 'totp', seed), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K32, 'totp', seed), function(ct, err) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
// Agent has K16 (L2), tries to decrypt L3 TOTP seed
safe(function() { return vault1984.crypto.decrypt_field(K16, 'totp', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K16, 'totp', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 key decrypted L3 TOTP seed to "' + pt + '"');
done();
@ -235,9 +235,9 @@
tests.push(function(done) {
var name = 'L2 TOTP seed accessible to agent (L2 key)';
var seed = 'JBSWY3DPEHPK3PXP';
resolve(vault1984.crypto.encrypt_field(K16, 'totp', seed), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K16, 'totp', seed), function(ct, err) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
resolve(vault1984.crypto.decrypt_field(K16, 'totp', ct), function(pt, err2) {
resolve(clavitor.crypto.decrypt_field(K16, 'totp', ct), function(pt, err2) {
if (err2) fail(name, err2.message);
else if (pt === seed) pass(name);
else fail(name, 'got "' + pt + '"');
@ -249,9 +249,9 @@
/* --- Test 15: L3 card number cannot be read with L2 key --- */
tests.push(function(done) {
var name = 'L3 card number inaccessible to agent (L2 key)';
resolve(vault1984.crypto.encrypt_field(K32, 'Number', '5452120017212208'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K32, 'Number', '5452120017212208'), function(ct, err) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'Number', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K16, 'Number', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 key decrypted L3 card number');
done();
@ -263,9 +263,9 @@
tests.push(function(done) {
var name = 'truncation: L2 prefix of L3, still cannot decrypt L3';
// K16 = K32[0..16] by design. Encrypt with full K32, try with K16.
resolve(vault1984.crypto.encrypt_field(K32, 'secret', 'classified'), function(ct, err) {
resolve(clavitor.crypto.encrypt_field(K32, 'secret', 'classified'), function(ct, err) {
if (err) { fail(name, 'encrypt failed'); done(); return; }
safe(function() { return vault1984.crypto.decrypt_field(K16, 'secret', ct); }, function(pt, err2) {
safe(function() { return clavitor.crypto.decrypt_field(K16, 'secret', ct); }, function(pt, err2) {
if (err2) pass(name);
else fail(name, 'L2 (prefix of L3) decrypted L3 data');
done();
@ -290,7 +290,7 @@
}
/* Return result string (for QuickJS eval or browser display) */
globalThis._v1984_test_result = summary;
globalThis._clavitor_test_result = summary;
return;
}
tests[idx](function() { run(idx + 1); });
@ -299,10 +299,10 @@
run(0);
/* For sync environments (QuickJS), result is available immediately */
if (typeof globalThis._v1984_test_result !== 'undefined') {
if (typeof globalThis._clavitor_test_result !== 'undefined') {
/* Used by CLI eval */
}
})();
/* Return result for jsbridge_eval */
globalThis._v1984_test_result || 'RUNNING (async — check console)';
globalThis._clavitor_test_result || 'RUNNING (async — check console)';

View File

@ -1,18 +1,18 @@
// Theme management (before anything else so it applies immediately, no flash)
var _themes = ['', 'theme-light', 'theme-midnight'];
var _currentTheme = localStorage.getItem('v1984_theme') || '';
var _currentTheme = localStorage.getItem('clavitor_theme') || '';
if (_currentTheme) document.body.className = _currentTheme;
function cycleTheme() {
var idx = _themes.indexOf(_currentTheme);
_currentTheme = _themes[(idx + 1) % _themes.length];
document.body.className = _currentTheme;
localStorage.setItem('v1984_theme', _currentTheme);
localStorage.setItem('clavitor_theme', _currentTheme);
}
// Stateless auth: L1 Bearer from master key in sessionStorage.
function getL1Bearer() {
var masterB64 = sessionStorage.getItem('v1984_master');
var masterB64 = sessionStorage.getItem('clavitor_master');
if (!masterB64) return null;
var bin = atob(masterB64);
var l1 = new Uint8Array(8);
@ -29,7 +29,7 @@ async function api(method, path, body) {
if (body) opts.body = JSON.stringify(body);
var res = await fetch(path, opts);
if (res.status === 401) {
sessionStorage.removeItem('v1984_master');
sessionStorage.removeItem('clavitor_master');
location.href = '/app/';
throw new Error('Unauthorized');
}
@ -42,7 +42,7 @@ var _countdownTimer = null;
var _countdownVal = 0;
function _resetIdle() {
if (!sessionStorage.getItem('v1984_master')) return;
if (!sessionStorage.getItem('clavitor_master')) return;
clearTimeout(_idleTimer);
_clearCountdown();
_idleTimer = setTimeout(_startCountdown, 60000);
@ -60,7 +60,7 @@ function _startCountdown() {
_countdownVal--;
if (_countdownVal <= 0) {
_clearCountdown();
sessionStorage.removeItem('v1984_master');
sessionStorage.removeItem('clavitor_master');
window.location.href = '/app/';
} else {
var el = document.getElementById('lockCountdownSec');
@ -94,11 +94,11 @@ function initTopbar() {
});
nav += '<button onclick="cycleTheme()" class="topbar-lock" style="font-size:0.75rem;opacity:0.6" title="Switch theme">Theme</button>';
nav += '<button onclick="if(typeof lockVault===\'function\'){lockVault()}else{sessionStorage.removeItem(\'v1984_master\');location.href=\'/app/\'}" class="topbar-lock">Lock</button>';
nav += '<button onclick="if(typeof lockVault===\'function\'){lockVault()}else{sessionStorage.removeItem(\'clavitor_master\');location.href=\'/app/\'}" class="topbar-lock">Lock</button>';
var logo = isVault
? '<span class="topbar-logo vaultname" onclick="if(typeof showAbout===\'function\')showAbout()" style="cursor:pointer">vault<span class="n">1984</span></span>'
: '<a href="/app/" class="topbar-logo vaultname">vault<span class="n">1984</span></a>';
? '<span class="topbar-logo vaultname" onclick="if(typeof showAbout===\'function\')showAbout()" style="cursor:pointer">clav<span class="n">itor</span></span>'
: '<a href="/app/" class="topbar-logo vaultname">clav<span class="n">itor</span></a>';
var el = document.getElementById('topbar');
if (el) {

View File

@ -107,8 +107,8 @@ function totp_remaining(period) {
}
/* Export */
if (typeof globalThis.vault1984 === 'undefined') globalThis.vault1984 = {};
globalThis.vault1984.totp = {
if (typeof globalThis.clavitor === 'undefined') globalThis.clavitor = {};
globalThis.clavitor.totp = {
generate_totp: generate_totp,
totp_remaining: totp_remaining,
base32_decode: base32_decode

View File

@ -11,7 +11,7 @@
(function(window) {
'use strict';
var SESSION_KEY = 'v1984_master';
var SESSION_KEY = 'clavitor_master';
var HKDF_SALT = new TextEncoder().encode('clavitor-master-v2');
// Derive master key from raw PRF output and store in sessionStorage.

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 118 B

After

Width:  |  Height:  |  Size: 118 B

View File

Before

Width:  |  Height:  |  Size: 87 B

After

Width:  |  Height:  |  Size: 87 B

View File

Before

Width:  |  Height:  |  Size: 100 B

After

Width:  |  Height:  |  Size: 100 B

View File

@ -1,4 +1,4 @@
module github.com/johanj/vault1984
module github.com/johanj/clavitor
go 1.24.0

View File

@ -35,7 +35,7 @@ func NormalizeKey(key []byte) []byte {
// L1 (8 bytes) is normalized to 16 → derives 16-byte key → AES-128-GCM.
func DeriveEntryKey(l1Key []byte, entryID int64) ([]byte, error) {
normalized := NormalizeKey(l1Key)
info := []byte("vault1984-entry-" + IDToHex(entryID))
info := []byte("clavitor-entry-" + IDToHex(entryID))
reader := hkdf.New(sha256.New, normalized, nil, info)
key := make([]byte, len(normalized)) // 16 bytes for AES-128
if _, err := io.ReadFull(reader, key); err != nil {
@ -47,7 +47,7 @@ func DeriveEntryKey(l1Key []byte, entryID int64) ([]byte, error) {
// DeriveHMACKey derives a separate HMAC key for blind indexes from L1.
func DeriveHMACKey(l1Key []byte) ([]byte, error) {
normalized := NormalizeKey(l1Key)
info := []byte("vault1984-hmac-index")
info := []byte("clavitor-hmac-index")
reader := hkdf.New(sha256.New, normalized, nil, info)
key := make([]byte, 32)
if _, err := io.ReadFull(reader, key); err != nil {

View File

@ -20,10 +20,11 @@ import (
// All fields zero/empty = telemetry disabled.
type TelemetryConfig struct {
FreqSeconds int // interval between POSTs (0 = disabled)
Host string // e.g. https://hq.vault1984.com/telemetry
Host string // e.g. https://hq.clavitor.com/telemetry
Token string // Bearer token for auth
DataDir string // vault data directory (to scan DBs)
Mode string // "self-hosted" or "hosted"
Version string // build version string
}
// TelemetryPayload is the JSON body posted to the telemetry endpoint.
@ -84,7 +85,7 @@ func CollectPayload(cfg TelemetryConfig, startTime time.Time) TelemetryPayload {
hostname, _ := os.Hostname()
return TelemetryPayload{
Version: "0.1.0",
Version: cfg.Version,
Hostname: hostname,
UptimeSeconds: int64(time.Since(startTime).Seconds()),
Timestamp: time.Now().UTC().Format(time.RFC3339),

View File

@ -36,13 +36,13 @@ func EnsureTLSCert(certPath, keyPath string) error {
tmpl := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{Organization: []string{"Vault1984"}, CommonName: "vault1984"},
Subject: pkix.Name{Organization: []string{"Clavitor"}, CommonName: "clavitor"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSNames: []string{"localhost", "vault1984.local"},
DNSNames: []string{"localhost", "clavitor.local"},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
}

View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
GO="${GO:-/usr/local/go/bin/go}"
MAC_HOST="${MAC_HOST:-johanjongsma@192.168.1.14}"
MAC_PROJECT="${MAC_PROJECT:-~/dev/clavitor/clavis/clavis-vault}"
OUT="dist"
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE=$(date -u +%Y%m%d-%H%M)
LDFLAGS="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildDate=${DATE}"
mkdir -p "$OUT"
echo "=== Clavitor release build ==="
echo " version: ${VERSION} (${COMMIT}) ${DATE}"
echo ""
# --- Linux amd64 (native) ---
echo "[1/4] linux/amd64 ..."
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 GOFIPS140=latest \
$GO build -ldflags "$LDFLAGS" -o "${OUT}/clavitor-linux-amd64" ./cmd/clavitor
echo " done"
# --- Linux arm64 (cross) ---
echo "[2/4] linux/arm64 ..."
if ! command -v aarch64-linux-gnu-gcc &>/dev/null; then
echo " aarch64-linux-gnu-gcc not found."
echo " Install with: sudo apt install gcc-aarch64-linux-gnu"
echo " SKIPPED"
else
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc GOFIPS140=latest \
$GO build -ldflags "$LDFLAGS" -o "${OUT}/clavitor-linux-arm64" ./cmd/clavitor
echo " done"
fi
# --- Darwin arm64 + amd64 (via SSH to Mac) ---
echo "[3/4] darwin/arm64 (via Mac SSH) ..."
echo "[4/4] darwin/amd64 (via Mac SSH) ..."
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$MAC_HOST" true 2>/dev/null; then
ssh "$MAC_HOST" bash -s <<REMOTE
set -euo pipefail
cd "${MAC_PROJECT}"
git pull --ff-only 2>/dev/null || true
VERSION=\$(git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT=\$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE=\$(date -u +%Y%m%d-%H%M)
LDFLAGS="-s -w -X main.version=\${VERSION} -X main.commit=\${COMMIT} -X main.buildDate=\${DATE}"
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
go build -ldflags "\$LDFLAGS" -o /tmp/clavitor-darwin-arm64 ./cmd/clavitor
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \
go build -ldflags "\$LDFLAGS" -o /tmp/clavitor-darwin-amd64 ./cmd/clavitor
REMOTE
scp "$MAC_HOST":/tmp/clavitor-darwin-arm64 "${OUT}/clavitor-darwin-arm64"
scp "$MAC_HOST":/tmp/clavitor-darwin-amd64 "${OUT}/clavitor-darwin-amd64"
echo " done"
else
echo " Mac unreachable at ${MAC_HOST} — SKIPPED"
fi
# --- Summary ---
echo ""
echo "=== Built artifacts ==="
for f in "${OUT}"/clavitor-*; do
[ -f "$f" ] || continue
size=$(du -h "$f" | cut -f1)
arch=$(file -b "$f" | head -c 80)
printf " %-30s %6s %s\n" "$(basename "$f")" "$size" "$arch"
done

Binary file not shown.

View File

@ -10,19 +10,19 @@
## Deployment
### Dev (localhost — Florida)
### Dev (forge = 192.168.1.16, Florida — dev.clavitor.ai)
```
make dev # build + restart locally
make deploy-dev # same thing
```
Dev runs on forge (localhost). `dev.clavitor.ai` DNS points to home IP.
### Prod (Zürich — zurich.inou.com)
### Prod (Zürich — zurich.inou.com — clavitor.ai)
```
make deploy-prod # cross-compile amd64, scp to Zürich, restart systemd
```
Prod runs at `/opt/clavitor-web/` as systemd service `clavitor-web`.
Caddy reverse proxies `clavitor.ai`, `clavitor.com`, `dev.clavitor.ai` → `localhost:8099`.
Caddy reverse proxies `clavitor.ai`, `clavitor.com`, `www.clavitor.ai`, `www.clavitor.com` → `localhost:8099`.
### First-time setup (already done)
```

Some files were not shown because too many files have changed in this diff Show More