name: pqc-secrets-node-integration description: Integrate ML-KEM-768 + AES-256-GCM key persistence into a Node.js server via a Python UV inline script source: auto-skill extracted_at: '2026-05-30T00:47:23.478Z'
PQC Secrets Integration for Node.js Servers
Integrate ML-KEM-768 + AES-256-GCM API key persistence into a Node.js/Express server using a companion Python UV inline script.
Architecture
System Keychain / File ~/.config/pqc-secrets/ (ML-KEM-768 private key) secrets.bundle.json (AES-256-GCM encrypted) │ │ ▼ ▼ pqc_secrets.py (UV script) ←── bin/pqc-secrets (shell wrapper) │ ▼ Node.js server: loadPqcSecrets() / persistPqcSecrets()
Step 1: Create the Python UV Script
Create scripts/pqc_secrets.py with inline dependency metadata:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "kyber-py>=0.2.0",
# "cryptography>=44.0",
# ]
# ///
Critical API gotcha: ML_KEM_768 is a singleton instance, not a class. Use ML_KEM_768.keygen() not ML_KEM_768().keygen().
Encaps return order: encaps(pk) returns (shared_secret, ciphertext) — shared_secret is 32 bytes (the AES key), ciphertext is 1088 bytes (the wrapped key to store). Do NOT swap these — decaps will fail with "Expected 1088 bytes and obtained 32".
Key commands to implement:
keygen— generate keypair, store private key in system keychain (macOS Keychain, Linux Secret Service) or fall back to an encrypted local filepack— readKEY=VALUElines from stdin, encaps shared_secret via ML-KEM-768, encrypt with AES-256-GCM, writesecrets.bundle.jsonexport— decaps via keychain or local file, decrypt, outputexport KEY=VALUElinesverify— same as export but only print key names, no values
Bundle format:
{
"version": 1,
"kem": { "algorithm": "ML-KEM-768", "ciphertext": "<1088-byte hex>" },
"data": { "algorithm": "AES-256-GCM", "nonce": "<12-byte hex>", "ciphertext": "<hex>" }
}
Step 2: Create the Shell Wrapper
bin/pqc-secrets resolves the script relative to the project root and delegates via uv run:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PQC_SCRIPT="${PROJECT_ROOT}/.agents/skills/pqc-secrets/scripts/pqc_secrets.py"
exec uv run "$PQC_SCRIPT" "$@"
Step 3: Integrate into Node.js Server
3a. Resolve the binary path
Use process.cwd() and __dirname as fallback candidates. Check X_OK with fs.accessSync:
function getPqcBinPath(): string {
const candidates = [
path.resolve(__dirname, '..', 'bin', 'pqc-secrets'),
path.resolve(process.cwd(), 'bin', 'pqc-secrets'),
];
for (const candidate of candidates) {
try { fs.accessSync(candidate, fs.constants.X_OK); return candidate; }
catch (_) { /* not found */ }
}
return '';
}
3b. Load secrets at startup
Call execFileSync with the export command, parse export KEY=VALUE lines, map env vars back to provider names:
function loadPqcSecrets(): void {
const bin = getPqcBinPath();
if (!bin) return;
const output = execFileSync(bin, ['export'], { encoding: 'utf8', timeout: 10000 });
for (const line of output.split('\n')) {
const match = line.match(/^export\s+(\w+)=(.+)$/);
if (!match) continue;
process.env[match[1]] = match[2];
const provider = findProviderByEnvVar(match[1]);
if (provider) keyStore[provider.name] = match[2];
}
}
3c. Persist on save/delete
Collect all in-memory keys, pipe as KEY=VALUE\n lines to pack via execFileSync with input:
function persistPqcSecrets(): void {
const bin = getPqcBinPath();
if (!bin) return;
const lines = Object.entries(keyStore)
.filter(([_, v]) => v)
.map(([name, val]) => `${getProviderSummary(name)!.keyEnvVar}=${val}`);
if (!lines.length) return;
execFileSync(bin, ['pack'], { input: lines.join('\n') + '\n', encoding: 'utf8', timeout: 10000 });
}
3d. Wire into routes
- Call
loadPqcSecrets()beforeapp.listen()(after other in-memory stores load) - Call
persistPqcSecrets()afterkeyStore[providerName] = keyValueinPOST /api/keys - Call
persistPqcSecrets()afterdelete keyStore[providerName]inDELETE /api/keys/:provider
Environment
uvmust be installed (pip install uvor Homebrew)kyber-pyandcryptographyauto-resolved byuv runfrom inline metadata- macOS Keychain used for private key storage (T2/M-series hardware-backed)
- Graceful degradation: if
bin/pqc-secretsnot found or bundle doesn't exist, server still works with in-memory-only keys - Set
LOCAL_ROUTER_DEV=truefor diagnostic log lines