@polpo-ai/vault-crypto 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenPolpo Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @polpo-ai/vault-crypto — AES-256-GCM encryption helpers for Polpo vault stores.
3
+ *
4
+ * Shared by EncryptedVaultStore (file-based) and DrizzleVaultStore (database-backed).
5
+ * Single source of truth for vault encryption logic.
6
+ *
7
+ * Key resolution:
8
+ * 1. POLPO_VAULT_KEY env var (hex-encoded 32 bytes) — for CI/Docker
9
+ * 2. ~/.polpo/vault.key file (auto-generated on first use) — for local dev
10
+ *
11
+ * Wire format: 12-byte IV | 16-byte auth tag | ciphertext (all as a single Buffer).
12
+ */
13
+ /**
14
+ * Resolve the encryption key from env var or key file.
15
+ * Auto-generates key file on first use.
16
+ */
17
+ export declare function resolveKey(): Buffer;
18
+ /**
19
+ * Encrypt a buffer with AES-256-GCM.
20
+ * Returns: IV (12 bytes) | auth tag (16 bytes) | ciphertext.
21
+ */
22
+ export declare function encrypt(data: Buffer, key: Buffer): Buffer;
23
+ /**
24
+ * Decrypt a buffer previously encrypted with `encrypt()`.
25
+ * Expects the same wire format: IV | auth tag | ciphertext.
26
+ */
27
+ export declare function decrypt(blob: Buffer, key: Buffer): Buffer;
28
+ /**
29
+ * Encrypt a JSON-serializable value. Returns a base64-encoded string
30
+ * suitable for storage in a TEXT/VARCHAR column.
31
+ */
32
+ export declare function encryptJson(value: unknown, key: Buffer): string;
33
+ /**
34
+ * Decrypt a base64-encoded string back to a parsed JSON value.
35
+ * Returns the fallback if decryption or parsing fails.
36
+ */
37
+ export declare function decryptJson<T>(encoded: string, key: Buffer, fallback: T): T;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAqCnC;AAID;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAUzD;AAID;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAQ3E"}
package/dist/index.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @polpo-ai/vault-crypto — AES-256-GCM encryption helpers for Polpo vault stores.
3
+ *
4
+ * Shared by EncryptedVaultStore (file-based) and DrizzleVaultStore (database-backed).
5
+ * Single source of truth for vault encryption logic.
6
+ *
7
+ * Key resolution:
8
+ * 1. POLPO_VAULT_KEY env var (hex-encoded 32 bytes) — for CI/Docker
9
+ * 2. ~/.polpo/vault.key file (auto-generated on first use) — for local dev
10
+ *
11
+ * Wire format: 12-byte IV | 16-byte auth tag | ciphertext (all as a single Buffer).
12
+ */
13
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { homedir } from "node:os";
17
+ // ── Constants ──
18
+ const ALGORITHM = "aes-256-gcm";
19
+ const IV_LENGTH = 12;
20
+ const AUTH_TAG_LENGTH = 16;
21
+ const KEY_LENGTH = 32; // 256 bits
22
+ const GLOBAL_KEY_DIR = join(homedir(), ".polpo");
23
+ const GLOBAL_KEY_FILE = join(GLOBAL_KEY_DIR, "vault.key");
24
+ // ── Key Management ──
25
+ /**
26
+ * Resolve the encryption key from env var or key file.
27
+ * Auto-generates key file on first use.
28
+ */
29
+ export function resolveKey() {
30
+ // 1. Check env var first (CI/Docker override)
31
+ const envKey = process.env.POLPO_VAULT_KEY;
32
+ if (envKey) {
33
+ const buf = Buffer.from(envKey, "hex");
34
+ if (buf.length !== KEY_LENGTH) {
35
+ throw new Error(`POLPO_VAULT_KEY must be ${KEY_LENGTH * 2} hex characters (${KEY_LENGTH} bytes). Got ${envKey.length} characters.`);
36
+ }
37
+ return buf;
38
+ }
39
+ // 2. Read or generate key file
40
+ if (existsSync(GLOBAL_KEY_FILE)) {
41
+ const raw = readFileSync(GLOBAL_KEY_FILE);
42
+ // Key file can be raw bytes or hex-encoded
43
+ if (raw.length === KEY_LENGTH)
44
+ return raw;
45
+ const hex = raw.toString("utf-8").trim();
46
+ const buf = Buffer.from(hex, "hex");
47
+ if (buf.length === KEY_LENGTH)
48
+ return buf;
49
+ throw new Error(`Invalid vault key file: ${GLOBAL_KEY_FILE}. Expected ${KEY_LENGTH} bytes.`);
50
+ }
51
+ // Auto-generate
52
+ if (!existsSync(GLOBAL_KEY_DIR)) {
53
+ mkdirSync(GLOBAL_KEY_DIR, { recursive: true });
54
+ }
55
+ const key = randomBytes(KEY_LENGTH);
56
+ writeFileSync(GLOBAL_KEY_FILE, key);
57
+ // Set restrictive permissions (owner-only)
58
+ try {
59
+ chmodSync(GLOBAL_KEY_FILE, 0o600);
60
+ }
61
+ catch {
62
+ // chmod may fail on Windows — non-fatal
63
+ }
64
+ return key;
65
+ }
66
+ // ── Low-level encrypt / decrypt ──
67
+ /**
68
+ * Encrypt a buffer with AES-256-GCM.
69
+ * Returns: IV (12 bytes) | auth tag (16 bytes) | ciphertext.
70
+ */
71
+ export function encrypt(data, key) {
72
+ const iv = randomBytes(IV_LENGTH);
73
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
74
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
75
+ const authTag = cipher.getAuthTag();
76
+ return Buffer.concat([iv, authTag, encrypted]);
77
+ }
78
+ /**
79
+ * Decrypt a buffer previously encrypted with `encrypt()`.
80
+ * Expects the same wire format: IV | auth tag | ciphertext.
81
+ */
82
+ export function decrypt(blob, key) {
83
+ if (blob.length < IV_LENGTH + AUTH_TAG_LENGTH) {
84
+ throw new Error("Vault data is corrupted (too short).");
85
+ }
86
+ const iv = blob.subarray(0, IV_LENGTH);
87
+ const authTag = blob.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
88
+ const ciphertext = blob.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
89
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
90
+ decipher.setAuthTag(authTag);
91
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
92
+ }
93
+ // ── JSON convenience helpers ──
94
+ /**
95
+ * Encrypt a JSON-serializable value. Returns a base64-encoded string
96
+ * suitable for storage in a TEXT/VARCHAR column.
97
+ */
98
+ export function encryptJson(value, key) {
99
+ const json = JSON.stringify(value);
100
+ const plain = Buffer.from(json, "utf-8");
101
+ const blob = encrypt(plain, key);
102
+ return blob.toString("base64");
103
+ }
104
+ /**
105
+ * Decrypt a base64-encoded string back to a parsed JSON value.
106
+ * Returns the fallback if decryption or parsing fails.
107
+ */
108
+ export function decryptJson(encoded, key, fallback) {
109
+ try {
110
+ const blob = Buffer.from(encoded, "base64");
111
+ const plain = decrypt(blob, key);
112
+ return JSON.parse(plain.toString("utf-8"));
113
+ }
114
+ catch {
115
+ return fallback;
116
+ }
117
+ }
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@polpo-ai/vault-crypto",
3
+ "version": "0.2.0",
4
+ "description": "AES-256-GCM encryption helpers for Polpo vault stores",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "sideEffects": false,
15
+ "devDependencies": {
16
+ "typescript": "^5.7.3"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "license": "MIT",
23
+ "author": "OpenPolpo Contributors",
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/lumea-labs/polpo.git",
33
+ "directory": "packages/vault-crypto"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "dev": "tsc --watch"
38
+ }
39
+ }