From 5978976b50473a101ef8bc42c1c5d767d90e69be Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Sat, 25 Apr 2026 01:35:04 -0700 Subject: [PATCH 1/8] feat: encrypt env blob with _VARLOCK_ENV_KEY for secure deployments When _VARLOCK_ENV_KEY (64-char hex) is set at build time, the resolved env blob injected into build output is encrypted with AES-256-GCM. At runtime, the init bundles detect the varlock:v1: prefix and decrypt using the same key from the runtime environment. This lets Vercel users set a single env var and have all other config travel encrypted inside the deployment artifact. - New crypto module with sync (node:crypto) and async (Web Crypto) paths - All integration injection points (Next.js, Vite) encrypt when key is present - _VARLOCK_ENV_KEY auto-excluded from injected blob and type generation - varlock generate-key CLI command with --plain flag for piping - Docs for Next.js and Vite integrations - Framework tests for encrypted blob flow --- .bumpy/encrypted-env-blob.md | 7 ++ .../frameworks/nextjs/nextjs-shared.ts | 26 +++++ .../vite-configs/vite.config.resolved-env.ts | 6 + .../frameworks/vite/vite-shared.ts | 31 ++++++ .../nextjs/src/turbopack-runtime-inject.ts | 8 +- .../integrations/nextjs/src/webpack-plugin.ts | 9 +- packages/integrations/vite/src/index.ts | 9 +- .../src/content/docs/integrations/nextjs.mdx | 45 ++++++++ .../src/content/docs/integrations/vite.mdx | 53 ++++++++- .../content/docs/reference/cli-commands.mdx | 10 ++ packages/varlock/package.json | 5 + packages/varlock/src/cli/cli-executable.ts | 2 + .../src/cli/commands/generate-key.command.ts | 24 ++++ .../varlock/src/env-graph/lib/env-graph.ts | 3 + .../src/env-graph/lib/type-generation.ts | 2 + packages/varlock/src/runtime/crypto.ts | 105 ++++++++++++++++++ packages/varlock/src/runtime/env.ts | 19 +++- packages/varlock/src/runtime/init-edge.ts | 10 ++ packages/varlock/src/runtime/init-server.ts | 8 ++ .../varlock/src/runtime/test/crypto.test.ts | 81 ++++++++++++++ packages/varlock/tsup.config.ts | 1 + 21 files changed, 458 insertions(+), 6 deletions(-) create mode 100644 .bumpy/encrypted-env-blob.md create mode 100644 framework-tests/frameworks/vite/files/vite-configs/vite.config.resolved-env.ts create mode 100644 packages/varlock/src/cli/commands/generate-key.command.ts create mode 100644 packages/varlock/src/runtime/crypto.ts create mode 100644 packages/varlock/src/runtime/test/crypto.test.ts diff --git a/.bumpy/encrypted-env-blob.md b/.bumpy/encrypted-env-blob.md new file mode 100644 index 000000000..2e3aa2221 --- /dev/null +++ b/.bumpy/encrypted-env-blob.md @@ -0,0 +1,7 @@ +--- +"varlock": minor +"@varlock/nextjs-integration": patch +"@varlock/vite-integration": patch +--- + +add _VARLOCK_ENV_KEY support to encrypt env blob in build output diff --git a/framework-tests/frameworks/nextjs/nextjs-shared.ts b/framework-tests/frameworks/nextjs/nextjs-shared.ts index c8a268eb5..9131f61e9 100644 --- a/framework-tests/frameworks/nextjs/nextjs-shared.ts +++ b/framework-tests/frameworks/nextjs/nextjs-shared.ts @@ -346,6 +346,32 @@ export function defineNextjsTests(nextVersion: number, testDir: string) { ], }); + nextEnv.describeScenario('encrypted env blob with _VARLOCK_ENV_KEY', { + command: buildCommand, + env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + templateFiles: { + 'app/page.tsx': 'pages/basic-page.tsx', + }, + fileAssertions: [ + { + description: 'runtime files contain encrypted blob (varlock:v1: prefix) instead of plaintext', + fileGlob: '.next/server/**/*runtime*.js', + shouldContain: ['varlock:v1:'], + shouldNotContain: ['super-secret-var'], + }, + { + description: 'prerendered HTML still has correct values (build uses plaintext env)', + fileGlob: '.next/**/*.html', + shouldContain: [ + 'next-prefixed-public-var', + 'unprefixed-public-var', + 'sensitive-var-available', + ], + shouldNotContain: ['super-secret-value'], + }, + ], + }); + nextEnv.describeScenario('leaky edge page', { command: buildCommand, templateFiles: { diff --git a/framework-tests/frameworks/vite/files/vite-configs/vite.config.resolved-env.ts b/framework-tests/frameworks/vite/files/vite-configs/vite.config.resolved-env.ts new file mode 100644 index 000000000..d5638c7e8 --- /dev/null +++ b/framework-tests/frameworks/vite/files/vite-configs/vite.config.resolved-env.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import { varlockVitePlugin } from '@varlock/vite-integration'; + +export default defineConfig({ + plugins: [varlockVitePlugin({ ssrInjectMode: 'resolved-env' })], +}); diff --git a/framework-tests/frameworks/vite/vite-shared.ts b/framework-tests/frameworks/vite/vite-shared.ts index dc73addeb..65e1ee793 100644 --- a/framework-tests/frameworks/vite/vite-shared.ts +++ b/framework-tests/frameworks/vite/vite-shared.ts @@ -256,6 +256,37 @@ export function defineViteTests( }); }); + // ---- Encrypted env blob ---- + + describe('encrypted env blob', () => { + viteEnv.describeScenario('SSR build with _VARLOCK_ENV_KEY encrypts the blob', { + command: 'vite build --ssr src/ssr-entry.ts', + env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + templateFiles: { + 'vite.config.ts': 'vite-configs/vite.config.resolved-env.ts', + 'index.html': 'html/basic.html', + 'src/ssr-entry.ts': 'pages/ssr-entry.ts', + }, + fileAssertions: [ + { + description: 'SSR output contains encrypted blob (varlock:v1: prefix)', + fileGlob: 'dist/*.js', + shouldContain: ['varlock:v1:'], + }, + { + description: 'SSR output does not contain plaintext secret', + fileGlob: 'dist/*.js', + shouldNotContain: ['super-secret-value'], + }, + { + description: 'public vars are still statically replaced', + fileGlob: 'dist/*.js', + shouldContain: ['public-test-value'], + }, + ], + }); + }); + // ---- Dev server ---- describe('dev server', () => { diff --git a/packages/integrations/nextjs/src/turbopack-runtime-inject.ts b/packages/integrations/nextjs/src/turbopack-runtime-inject.ts index 0e0c1c5fd..5b680b1b7 100644 --- a/packages/integrations/nextjs/src/turbopack-runtime-inject.ts +++ b/packages/integrations/nextjs/src/turbopack-runtime-inject.ts @@ -1,5 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; +import { encryptEnvBlobSync } from 'varlock/encrypt-env'; function debug(...args: Array) { if (!process.env.DEBUG_VARLOCK_NEXT_INTEGRATION) return; @@ -25,6 +26,11 @@ export function injectVarlockInitIntoTurbopackRuntime(nextDirPath: string) { return; } + let envPayload = rawEnv; + if (process.env._VARLOCK_ENV_KEY) { + envPayload = encryptEnvBlobSync(rawEnv, process.env._VARLOCK_ENV_KEY); + } + // Find turbopack runtime files ([turbopack]_runtime.js) and edge-wrapper files. // Node.js SSR/build uses [turbopack]_runtime.js, while edge runtime uses // edge-wrapper JS files (no [turbopack]_runtime.js exists for edge). @@ -58,7 +64,7 @@ export function injectVarlockInitIntoTurbopackRuntime(nextDirPath: string) { // Load both init bundles — server (full, node:zlib/node:http) and edge (no node builtins) const initServerSrc = fs.readFileSync(require.resolve('varlock/init-server'), 'utf8'); const initEdgeSrc = fs.readFileSync(require.resolve('varlock/init-edge'), 'utf8'); - const envInline = `process.env.__VARLOCK_ENV = process.env.__VARLOCK_ENV || ${JSON.stringify(rawEnv)};`; + const envInline = `process.env.__VARLOCK_ENV = process.env.__VARLOCK_ENV || ${JSON.stringify(envPayload)};`; // The CJS init bundles use `exports.X = ...` at the end, so we must provide // a dummy `exports` object when wrapping in an IIFE to avoid ReferenceError. diff --git a/packages/integrations/nextjs/src/webpack-plugin.ts b/packages/integrations/nextjs/src/webpack-plugin.ts index 469e116de..20b805354 100644 --- a/packages/integrations/nextjs/src/webpack-plugin.ts +++ b/packages/integrations/nextjs/src/webpack-plugin.ts @@ -6,6 +6,7 @@ import { import { patchGlobalServerResponse } from 'varlock/patch-server-response'; import { type SerializedEnvGraph } from 'varlock'; +import { encryptEnvBlobSync } from 'varlock/encrypt-env'; import type { NextConfig } from 'next'; const WEBPACK_PLUGIN_NAME = 'VarlockNextWebpackPlugin'; @@ -173,8 +174,12 @@ export function createWebpackConfigFn( // inline the resolved env so it's baked into the build // this removes the need for a .env.production.local file on platforms like Vercel const rawEnv = process.env.__VARLOCK_ENV; - const envInline = rawEnv - ? `process.env.__VARLOCK_ENV = process.env.__VARLOCK_ENV || ${JSON.stringify(rawEnv)};` + let envPayload = rawEnv; + if (rawEnv && process.env._VARLOCK_ENV_KEY) { + envPayload = encryptEnvBlobSync(rawEnv, process.env._VARLOCK_ENV_KEY); + } + const envInline = envPayload + ? `process.env.__VARLOCK_ENV = process.env.__VARLOCK_ENV || ${JSON.stringify(envPayload)};` : ''; const updatedSourceStr = [ diff --git a/packages/integrations/vite/src/index.ts b/packages/integrations/vite/src/index.ts index dff32f9d0..efcca568d 100644 --- a/packages/integrations/vite/src/index.ts +++ b/packages/integrations/vite/src/index.ts @@ -10,6 +10,7 @@ import { patchGlobalServerResponse } from 'varlock/patch-server-response'; import { patchGlobalResponse } from 'varlock/patch-response'; import { createDebug, type SerializedEnvGraph } from 'varlock'; import { execSyncVarlock, VarlockExecError } from 'varlock/exec-sync-varlock'; +import { encryptEnvBlobSync } from 'varlock/encrypt-env'; import { createReplacerTransformFn, SUPPORTED_FILES } from './transform'; @@ -170,7 +171,13 @@ export function varlockVitePlugin( lines.push("import 'varlock/auto-load';"); } else { if (ssrInjectMode === 'resolved-env') { - lines.push(`globalThis.__varlockLoadedEnv = ${JSON.stringify(varlockLoadedEnv)};`); + const serialized = JSON.stringify(varlockLoadedEnv); + if (process.env._VARLOCK_ENV_KEY) { + const encrypted = encryptEnvBlobSync(serialized, process.env._VARLOCK_ENV_KEY); + lines.push(`globalThis.__varlockLoadedEnv = ${JSON.stringify(encrypted)};`); + } else { + lines.push(`globalThis.__varlockLoadedEnv = ${JSON.stringify(varlockLoadedEnv)};`); + } } // inject custom entry code from integrations (e.g., CF bindings loader) diff --git a/packages/varlock-website/src/content/docs/integrations/nextjs.mdx b/packages/varlock-website/src/content/docs/integrations/nextjs.mdx index ec3c3a255..0ab7cbbe0 100644 --- a/packages/varlock-website/src/content/docs/integrations/nextjs.mdx +++ b/packages/varlock-website/src/content/docs/integrations/nextjs.mdx @@ -325,6 +325,51 @@ varlock run -- node .next/standalone/server.js ``` +--- + +## Encrypting the env blob ||encrypting-the-env-blob|| + +When deploying to platforms like Vercel, varlock injects the fully resolved env data into your build output so it's available at runtime without needing the CLI or filesystem access. By default, this blob is plaintext JSON — meaning anyone with access to the build artifact can read your secrets. + +To encrypt this blob, set the `_VARLOCK_ENV_KEY` environment variable with a 256-bit hex key. When present at build time, the blob is encrypted with AES-256-GCM before being injected. At runtime, the init bundle decrypts it using the same key from the runtime environment. + + + +1. **Generate and set the key on Vercel** + + This one-liner generates a key and sets it as a sensitive env var on Vercel for all environments: + + ```bash + varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY production preview development --sensitive + ``` + + Or generate and set it manually on your platform — the key must be available at both **build time** (for encryption) and **runtime** (for decryption): + + + +3. **Optionally add it to your `.env.local` for local builds** + + If you want local builds to also encrypt the blob (e.g., to test the flow), add the key to `.env.local`: + + ```env title=".env.local" + _VARLOCK_ENV_KEY=your-64-char-hex-key-here + ``` + + + +:::tip +You can define `_VARLOCK_ENV_KEY` in your `.env.schema` to enable validation (e.g., required in production). It will automatically be excluded from the injected blob and from type generation — it's infrastructure, not application config. + +```env-spec title=".env.schema" +# @sensitive +_VARLOCK_ENV_KEY= +``` +::: + +:::note +The encryption key is **never baked into the build**. It must always come from the runtime environment (e.g., a Vercel environment variable). The encrypted blob is useless without it. +::: + --- ## Troubleshooting diff --git a/packages/varlock-website/src/content/docs/integrations/vite.mdx b/packages/varlock-website/src/content/docs/integrations/vite.mdx index baf4da6b1..56186699e 100644 --- a/packages/varlock-website/src/content/docs/integrations/vite.mdx +++ b/packages/varlock-website/src/content/docs/integrations/vite.mdx @@ -118,7 +118,7 @@ varlockVitePlugin({ ssrInjectMode: 'auto-load' }) - `init-only` - injects varlock initialization code, but does not load the env vars. You must still boot your app via `varlock run` in this mode. - `auto-load` - injects `import 'varlock/auto-load';` to load your resolved env via the varlock CLI -- `resolved-env` - injects the fully resolved env data into your built code. This is useful in environments like Vercel/Cloudflare/etc where you have no control over your build command, and limited access to use CLI commands or the filesystem +- `resolved-env` - injects the fully resolved env data into your built code. This is useful in environments like Vercel/Cloudflare/etc where you have no control over your build command, and limited access to use CLI commands or the filesystem. See the [encrypting the env blob](#encrypting-the-env-blob) section for more information. **If not specified, we will attempt to infer the correct mode based on the presence of other vite plugins and environment variables, which give us hints about how your application will be run.** Otherwise defaulting to `init-only`. @@ -241,6 +241,57 @@ All non-sensitive items are bundled at build time via `ENV`, while `import.meta. +--- + +## Encrypting the env blob ||encrypting-the-env-blob|| + +When using `ssrInjectMode: 'resolved-env'`, varlock injects the fully resolved env data into your SSR build output. By default, this blob is plaintext JSON — meaning anyone with access to the build artifact can read your secrets. + +To encrypt this blob, set the `_VARLOCK_ENV_KEY` environment variable with a 256-bit hex key. When present at build time, the blob is encrypted with AES-256-GCM before being injected. At runtime, it's decrypted using the same key from the runtime environment. + + + +1. **Generate and set the key on your platform** + + For Vercel, this one-liner generates a key and sets it as a sensitive env var for all environments: + + ```bash + varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY production preview development --sensitive + ``` + + Or generate and set it manually — the key must be available at both **build time** (for encryption) and **runtime** (for decryption): + + + +2. **Ensure you're using `resolved-env` mode** + + Encryption only applies when the blob is inlined into the build: + + ```ts title="vite.config.ts" + varlockVitePlugin({ ssrInjectMode: 'resolved-env' }) + ``` + +3. **Optionally add it to your `.env.local` for local builds** + + ```env title=".env.local" + _VARLOCK_ENV_KEY=your-64-char-hex-key-here + ``` + + + +:::tip +You can define `_VARLOCK_ENV_KEY` in your `.env.schema` to enable validation. It will automatically be excluded from the injected blob and from type generation. + +```env-spec title=".env.schema" +# @sensitive +_VARLOCK_ENV_KEY= +``` +::: + +:::note +The encryption key is **never baked into the build**. It must always come from the runtime environment. The encrypted blob is useless without it. +::: + --- ## Reference diff --git a/packages/varlock-website/src/content/docs/reference/cli-commands.mdx b/packages/varlock-website/src/content/docs/reference/cli-commands.mdx index 240cbd975..606c1518e 100644 --- a/packages/varlock-website/src/content/docs/reference/cli-commands.mdx +++ b/packages/varlock-website/src/content/docs/reference/cli-commands.mdx @@ -508,6 +508,16 @@ You can also temporarily opt out by setting the `VARLOCK_TELEMETRY_DISABLED` env
+### `varlock generate-key` ||generate-key|| + +Generates a random 256-bit encryption key for use with `_VARLOCK_ENV_KEY`. This key is used to encrypt the resolved env blob that gets baked into your build output on certain frameworks/platforms. + +```bash +varlock generate-key +``` + +See the [Next.js](/integrations/nextjs/#encrypting-the-env-blob) and [Vite](/integrations/vite/#encrypting-the-env-blob) integration docs for setup instructions. + ### `varlock help` ||help|| Displays general help information, alias for `varlock --help` diff --git a/packages/varlock/package.json b/packages/varlock/package.json index f1cdb35a1..3c182d5dd 100644 --- a/packages/varlock/package.json +++ b/packages/varlock/package.json @@ -102,6 +102,11 @@ "types": "./dist/runtime/init-edge.d.cts", "default": "./dist/runtime/init-edge.cjs" }, + "./encrypt-env": { + "ts-src": "./src/runtime/crypto.ts", + "types": "./dist/runtime/crypto.d.ts", + "default": "./dist/runtime/crypto.js" + }, "./exec-sync-varlock": { "ts-src": "./src/lib/exec-sync-varlock.ts", "types": "./dist/lib/exec-sync-varlock.d.ts", diff --git a/packages/varlock/src/cli/cli-executable.ts b/packages/varlock/src/cli/cli-executable.ts index 7a270e7eb..c3255f948 100644 --- a/packages/varlock/src/cli/cli-executable.ts +++ b/packages/varlock/src/cli/cli-executable.ts @@ -27,6 +27,7 @@ import { commandSpec as scanCommandSpec } from './commands/scan.command'; import { commandSpec as typegenCommandSpec } from './commands/typegen.command'; import { commandSpec as installPluginCommandSpec } from './commands/install-plugin.command'; import { commandSpec as auditCommandSpec } from './commands/audit.command'; +import { commandSpec as generateKeyCommandSpec } from './commands/generate-key.command'; // import { commandSpec as loginCommandSpec } from './commands/login.command'; // import { commandSpec as pluginCommandSpec } from './commands/plugin.command'; @@ -67,6 +68,7 @@ subCommands.set('scan', buildLazyCommand(scanCommandSpec, async () => await impo subCommands.set('audit', buildLazyCommand(auditCommandSpec, async () => await import('./commands/audit.command'))); subCommands.set('typegen', buildLazyCommand(typegenCommandSpec, async () => await import('./commands/typegen.command'))); subCommands.set('install-plugin', buildLazyCommand(installPluginCommandSpec, async () => await import('./commands/install-plugin.command'))); +subCommands.set('generate-key', buildLazyCommand(generateKeyCommandSpec, async () => await import('./commands/generate-key.command'))); // subCommands.set('login', buildLazyCommand(loginCommandSpec, async () => await import('./commands/login.command'))); // subCommands.set('plugin', buildLazyCommand(pluginCommandSpec, async () => await import('./commands/plugin.command'))); diff --git a/packages/varlock/src/cli/commands/generate-key.command.ts b/packages/varlock/src/cli/commands/generate-key.command.ts new file mode 100644 index 000000000..13a2fc1a7 --- /dev/null +++ b/packages/varlock/src/cli/commands/generate-key.command.ts @@ -0,0 +1,24 @@ +import { define } from 'gunshi'; + +import { generateEncryptionKeyHex } from '../../runtime/crypto'; +import { type TypedGunshiCommandFn } from '../helpers/gunshi-type-utils'; + +export const commandSpec = define({ + name: 'generate-key', + description: 'Generate an encryption key for encrypting the env blob in deployments', + args: {}, +}); + +export const commandFn: TypedGunshiCommandFn = async () => { + const key = generateEncryptionKeyHex(); + + console.log(''); + console.log('Generated _VARLOCK_ENV_KEY:'); + console.log(''); + console.log(` ${key}`); + console.log(''); + console.log('Set this as an environment variable on your deployment platform (e.g., Vercel, Cloudflare).'); + console.log('When _VARLOCK_ENV_KEY is present at build time, the resolved env blob will be'); + console.log('encrypted before being injected into the build output, and decrypted at runtime.'); + console.log(''); +}; diff --git a/packages/varlock/src/env-graph/lib/env-graph.ts b/packages/varlock/src/env-graph/lib/env-graph.ts index 718035cba..346eda17b 100644 --- a/packages/varlock/src/env-graph/lib/env-graph.ts +++ b/packages/varlock/src/env-graph/lib/env-graph.ts @@ -545,6 +545,9 @@ export class EnvGraph { }); } for (const itemKey of this.sortedConfigKeys) { + // _VARLOCK_ENV_KEY is used to encrypt/decrypt the blob itself — including it + // would be redundant (the runtime already has it via process.env) and wasteful. + if (itemKey === '_VARLOCK_ENV_KEY') continue; const item = this.configSchema[itemKey]; serializedGraph.config[itemKey] = { value: item.resolvedValue, diff --git a/packages/varlock/src/env-graph/lib/type-generation.ts b/packages/varlock/src/env-graph/lib/type-generation.ts index 261da127b..a280a762a 100644 --- a/packages/varlock/src/env-graph/lib/type-generation.ts +++ b/packages/varlock/src/env-graph/lib/type-generation.ts @@ -246,6 +246,8 @@ export async function generateTypes(graph: EnvGraph, lang: string, typesPath: st // Skip items that exist only in env-specific sources const items: Array = []; for (const itemKey of graph.sortedConfigKeys) { + // _VARLOCK_ENV_KEY is infrastructure — not accessed via ENV proxy + if (itemKey === '_VARLOCK_ENV_KEY') continue; const configItem = graph.configSchema[itemKey]; if (!configItem.defsForTypeGeneration.length) continue; items.push(await configItem.getTypeGenInfo()); diff --git a/packages/varlock/src/runtime/crypto.ts b/packages/varlock/src/runtime/crypto.ts new file mode 100644 index 000000000..bf054c5a0 --- /dev/null +++ b/packages/varlock/src/runtime/crypto.ts @@ -0,0 +1,105 @@ +/** + * Encrypt/decrypt utilities for the varlock env blob. + * + * Uses AES-256-GCM with a 12-byte random IV. + * Encrypted format: "varlock:v1:" + base64(iv[12] + ciphertext + authTag[16]) + * + * Sync versions use Node.js `node:crypto` (build-time + init-server). + * Async version uses Web Crypto API (init-edge, where node:crypto is unavailable). + */ + +const ENCRYPTED_PREFIX = 'varlock:v1:'; +const IV_LENGTH = 12; +const AUTH_TAG_LENGTH = 16; +const KEY_LENGTH_HEX = 64; // 32 bytes = 64 hex chars + +export function isEncryptedBlob(value: string): boolean { + return value.startsWith(ENCRYPTED_PREFIX); +} + +function validateHexKey(hexKey: string): void { + if (hexKey.length !== KEY_LENGTH_HEX || !/^[0-9a-f]+$/i.test(hexKey)) { + throw new Error(`[varlock] _VARLOCK_ENV_KEY must be a ${KEY_LENGTH_HEX}-character hex string (256 bits)`); + } +} + +function hexToBytes(hex: string): Uint8Array { + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return bytes; +} + +// -- Sync (Node.js node:crypto) ------------------------------------------ + +export function encryptEnvBlobSync(json: string, hexKey: string): string { + validateHexKey(hexKey); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const crypto = require('node:crypto'); + const keyBytes = hexToBytes(hexKey); + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv('aes-256-gcm', keyBytes, iv); + const encrypted = Buffer.concat([cipher.update(json, 'utf8'), cipher.final()]); + const authTag = cipher.getAuthTag(); + const combined = Buffer.concat([iv, encrypted, authTag]); + return ENCRYPTED_PREFIX + combined.toString('base64'); +} + +export function decryptEnvBlobSync(encrypted: string, hexKey: string): string { + validateHexKey(hexKey); + if (!isEncryptedBlob(encrypted)) { + throw new Error('[varlock] expected encrypted blob with varlock:v1: prefix'); + } + // eslint-disable-next-line @typescript-eslint/no-require-imports + const crypto = require('node:crypto'); + const combined = Buffer.from(encrypted.slice(ENCRYPTED_PREFIX.length), 'base64'); + const iv = combined.subarray(0, IV_LENGTH); + const authTag = combined.subarray(combined.length - AUTH_TAG_LENGTH); + const ciphertext = combined.subarray(IV_LENGTH, combined.length - AUTH_TAG_LENGTH); + const decipher = crypto.createDecipheriv('aes-256-gcm', hexToBytes(hexKey), iv); + decipher.setAuthTag(authTag); + return decipher.update(ciphertext, undefined, 'utf8') + decipher.final('utf8'); +} + +// -- Async (Web Crypto API, edge-compatible) ------------------------------ +// Currently unused — all init paths use the sync version since every major edge +// runtime now supports node:crypto (Vercel Edge, Cloudflare with nodejs_compat, Deno). +// Kept as a public export in case consumers need to decrypt in a pure Web Crypto context. + +export async function decryptEnvBlobAsync(encrypted: string, hexKey: string): Promise { + validateHexKey(hexKey); + if (!isEncryptedBlob(encrypted)) { + throw new Error('[varlock] expected encrypted blob with varlock:v1: prefix'); + } + const raw = atob(encrypted.slice(ENCRYPTED_PREFIX.length)); + const combined = new Uint8Array(raw.length); + for (let i = 0; i < raw.length; i++) combined[i] = raw.charCodeAt(i); + + const iv = combined.slice(0, IV_LENGTH); + // Web Crypto expects ciphertext + authTag concatenated (no separation needed) + const ciphertextWithTag = combined.slice(IV_LENGTH); + + const keyBytes = hexToBytes(hexKey); + const key = await globalThis.crypto.subtle.importKey( + 'raw', + keyBytes.buffer as ArrayBuffer, + { name: 'AES-GCM' }, + false, + ['decrypt'], + ); + const decrypted = await globalThis.crypto.subtle.decrypt( + { name: 'AES-GCM', iv }, + key, + ciphertextWithTag, + ); + return new TextDecoder().decode(decrypted); +} + +// -- Key generation ------------------------------------------------------- + +export function generateEncryptionKeyHex(): string { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const crypto = require('node:crypto'); + return (crypto.randomBytes(32) as Buffer).toString('hex'); +} diff --git a/packages/varlock/src/runtime/env.ts b/packages/varlock/src/runtime/env.ts index 08b203c24..130b0647d 100644 --- a/packages/varlock/src/runtime/env.ts +++ b/packages/varlock/src/runtime/env.ts @@ -248,7 +248,24 @@ export function initVarlockEnv(opts?: { let serializedEnvData: SerializedEnvGraph; // when we inject resolved config at build time, we store it here if ((globalThis as any).__varlockLoadedEnv) { - serializedEnvData = (globalThis as any).__varlockLoadedEnv; + let loaded = (globalThis as any).__varlockLoadedEnv; + // if the blob was encrypted at build time, it will be a string rather than an object + if (typeof loaded === 'string') { + // Lazy-import to avoid pulling node:crypto into browser/frontend bundles. + // The init bundles (init-server/init-edge) handle the process.env.__VARLOCK_ENV + // path themselves, so this branch only fires for the globalThis path + // (Vite resolved-env, Cloudflare). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { isEncryptedBlob, decryptEnvBlobSync } = require('./crypto'); + if (isEncryptedBlob(loaded)) { + const key = processExists ? process.env._VARLOCK_ENV_KEY : undefined; + if (!key) throw new Error('[varlock] __varlockLoadedEnv is encrypted but _VARLOCK_ENV_KEY is not set'); + loaded = decryptEnvBlobSync(loaded, key); + } + loaded = JSON.parse(loaded); + (globalThis as any).__varlockLoadedEnv = loaded; + } + serializedEnvData = loaded; // otherwise if we inject via `varlock run` or have already loaded, it will be in process.env } else if (processExists && process.env.__VARLOCK_ENV) { diff --git a/packages/varlock/src/runtime/init-edge.ts b/packages/varlock/src/runtime/init-edge.ts index 032e66ed2..7e477d2ab 100644 --- a/packages/varlock/src/runtime/init-edge.ts +++ b/packages/varlock/src/runtime/init-edge.ts @@ -6,6 +6,16 @@ import { initVarlockEnv } from '../runtime/env'; import { patchGlobalConsole } from '../runtime/patch-console'; import { patchGlobalResponse } from '../runtime/patch-response'; +import { isEncryptedBlob, decryptEnvBlobSync } from '../runtime/crypto'; + +// Decrypt the env blob if it was encrypted at build time. +// Modern edge runtimes (Vercel Edge, Cloudflare with nodejs_compat) support node:crypto, +// so we use the sync version here. Pure Web Crypto async path is available in env.ts as fallback. +if (process.env.__VARLOCK_ENV && isEncryptedBlob(process.env.__VARLOCK_ENV)) { + const key = process.env._VARLOCK_ENV_KEY; + if (!key) throw new Error('[varlock] __VARLOCK_ENV is encrypted but _VARLOCK_ENV_KEY is not set'); + process.env.__VARLOCK_ENV = decryptEnvBlobSync(process.env.__VARLOCK_ENV, key); +} initVarlockEnv(); patchGlobalConsole(); diff --git a/packages/varlock/src/runtime/init-server.ts b/packages/varlock/src/runtime/init-server.ts index e1e3b3a17..d4afae951 100644 --- a/packages/varlock/src/runtime/init-server.ts +++ b/packages/varlock/src/runtime/init-server.ts @@ -6,6 +6,14 @@ import { initVarlockEnv } from '../runtime/env'; import { patchGlobalConsole } from '../runtime/patch-console'; import { patchGlobalServerResponse } from '../runtime/patch-server-response'; import { patchGlobalResponse } from '../runtime/patch-response'; +import { isEncryptedBlob, decryptEnvBlobSync } from '../runtime/crypto'; + +// Decrypt the env blob if it was encrypted at build time +if (process.env.__VARLOCK_ENV && isEncryptedBlob(process.env.__VARLOCK_ENV)) { + const key = process.env._VARLOCK_ENV_KEY; + if (!key) throw new Error('[varlock] __VARLOCK_ENV is encrypted but _VARLOCK_ENV_KEY is not set'); + process.env.__VARLOCK_ENV = decryptEnvBlobSync(process.env.__VARLOCK_ENV, key); +} initVarlockEnv(); patchGlobalConsole(); diff --git a/packages/varlock/src/runtime/test/crypto.test.ts b/packages/varlock/src/runtime/test/crypto.test.ts new file mode 100644 index 000000000..2b488942a --- /dev/null +++ b/packages/varlock/src/runtime/test/crypto.test.ts @@ -0,0 +1,81 @@ +import { describe, it, expect } from 'vitest'; +import { + encryptEnvBlobSync, + decryptEnvBlobSync, + decryptEnvBlobAsync, + isEncryptedBlob, + generateEncryptionKeyHex, +} from '../crypto'; + +const TEST_KEY = 'a'.repeat(64); // valid 256-bit hex key +const TEST_JSON = JSON.stringify({ + config: { API_KEY: { value: 'secret-123', isSensitive: true } }, + sources: [], + settings: {}, +}); + +describe('crypto', () => { + describe('isEncryptedBlob', () => { + it('returns true for varlock:v1: prefixed strings', () => { + expect(isEncryptedBlob('varlock:v1:abc')).toBe(true); + }); + it('returns false for plain JSON', () => { + expect(isEncryptedBlob('{"config":{}}')).toBe(false); + }); + }); + + describe('generateEncryptionKeyHex', () => { + it('generates a 64-character hex string', () => { + const key = generateEncryptionKeyHex(); + expect(key).toMatch(/^[0-9a-f]{64}$/); + }); + it('generates unique keys', () => { + const a = generateEncryptionKeyHex(); + const b = generateEncryptionKeyHex(); + expect(a).not.toBe(b); + }); + }); + + describe('sync encrypt/decrypt', () => { + it('round-trips correctly', () => { + const encrypted = encryptEnvBlobSync(TEST_JSON, TEST_KEY); + expect(isEncryptedBlob(encrypted)).toBe(true); + const decrypted = decryptEnvBlobSync(encrypted, TEST_KEY); + expect(decrypted).toBe(TEST_JSON); + }); + + it('produces different ciphertext each time (random IV)', () => { + const a = encryptEnvBlobSync(TEST_JSON, TEST_KEY); + const b = encryptEnvBlobSync(TEST_JSON, TEST_KEY); + expect(a).not.toBe(b); + }); + + it('rejects invalid key length', () => { + expect(() => encryptEnvBlobSync(TEST_JSON, 'tooshort')).toThrow('64-character hex string'); + }); + + it('rejects wrong key on decrypt', () => { + const encrypted = encryptEnvBlobSync(TEST_JSON, TEST_KEY); + const wrongKey = 'b'.repeat(64); + expect(() => decryptEnvBlobSync(encrypted, wrongKey)).toThrow(); + }); + + it('rejects non-encrypted input on decrypt', () => { + expect(() => decryptEnvBlobSync('plain text', TEST_KEY)).toThrow('varlock:v1: prefix'); + }); + }); + + describe('async decrypt (Web Crypto)', () => { + it('decrypts what sync encrypted', async () => { + const encrypted = encryptEnvBlobSync(TEST_JSON, TEST_KEY); + const decrypted = await decryptEnvBlobAsync(encrypted, TEST_KEY); + expect(decrypted).toBe(TEST_JSON); + }); + + it('rejects wrong key', async () => { + const encrypted = encryptEnvBlobSync(TEST_JSON, TEST_KEY); + const wrongKey = 'b'.repeat(64); + await expect(decryptEnvBlobAsync(encrypted, wrongKey)).rejects.toThrow(); + }); + }); +}); diff --git a/packages/varlock/tsup.config.ts b/packages/varlock/tsup.config.ts index 56fa7c54e..4a059d610 100644 --- a/packages/varlock/tsup.config.ts +++ b/packages/varlock/tsup.config.ts @@ -10,6 +10,7 @@ export default defineConfig([ 'src/runtime/patch-console.ts', 'src/runtime/patch-response.ts', + 'src/runtime/crypto.ts', 'src/env.ts', 'src/auto-load.ts', 'src/dotenv-compat.ts', // exposed under `/config` to match dotenv From 4f5b773153a8a72f8576cdff2b174ae8eab03292 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Fri, 29 May 2026 22:41:55 -0700 Subject: [PATCH 2/8] chore: update bump file for cloudflare and expo integrations --- .bumpy/encrypted-env-blob.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.bumpy/encrypted-env-blob.md b/.bumpy/encrypted-env-blob.md index 2e3aa2221..1e7dec443 100644 --- a/.bumpy/encrypted-env-blob.md +++ b/.bumpy/encrypted-env-blob.md @@ -2,6 +2,8 @@ "varlock": minor "@varlock/nextjs-integration": patch "@varlock/vite-integration": patch +"@varlock/cloudflare-integration": patch +"@varlock/expo-integration": patch --- -add _VARLOCK_ENV_KEY support to encrypt env blob in build output +add @encryptInjectedEnv and @disableProcessEnvInjection root decorators for encrypted deployments From 71dc215b5702779fd6eab476a98a128ce43190b2 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Fri, 29 May 2026 23:21:00 -0700 Subject: [PATCH 3/8] fix: use correct glob pattern for nextjs encrypted blob test The test was looking for `.next/server/**/*runtime*.js` which doesn't match webpack output files. Changed to `.next/server/**/*.js` and removed the prerendered HTML assertion since the page is dynamic. --- .../frameworks/nextjs/nextjs-shared.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/framework-tests/frameworks/nextjs/nextjs-shared.ts b/framework-tests/frameworks/nextjs/nextjs-shared.ts index 9131f61e9..505b62b38 100644 --- a/framework-tests/frameworks/nextjs/nextjs-shared.ts +++ b/framework-tests/frameworks/nextjs/nextjs-shared.ts @@ -352,23 +352,14 @@ export function defineNextjsTests(nextVersion: number, testDir: string) { templateFiles: { 'app/page.tsx': 'pages/basic-page.tsx', }, + expectSuccess: true, fileAssertions: [ { - description: 'runtime files contain encrypted blob (varlock:v1: prefix) instead of plaintext', - fileGlob: '.next/server/**/*runtime*.js', + description: 'server JS files contain encrypted blob (varlock:v1: prefix) instead of plaintext', + fileGlob: '.next/server/**/*.js', shouldContain: ['varlock:v1:'], shouldNotContain: ['super-secret-var'], }, - { - description: 'prerendered HTML still has correct values (build uses plaintext env)', - fileGlob: '.next/**/*.html', - shouldContain: [ - 'next-prefixed-public-var', - 'unprefixed-public-var', - 'sensitive-var-available', - ], - shouldNotContain: ['super-secret-value'], - }, ], }); From ef80c3292f0f9a90c9daad6cac45a00d45b39125 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Fri, 29 May 2026 23:28:32 -0700 Subject: [PATCH 4/8] test: add expectSuccess to vite encrypted blob test for debugging --- framework-tests/frameworks/vite/vite-shared.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/framework-tests/frameworks/vite/vite-shared.ts b/framework-tests/frameworks/vite/vite-shared.ts index 65e1ee793..ec580ea34 100644 --- a/framework-tests/frameworks/vite/vite-shared.ts +++ b/framework-tests/frameworks/vite/vite-shared.ts @@ -267,6 +267,7 @@ export function defineViteTests( 'index.html': 'html/basic.html', 'src/ssr-entry.ts': 'pages/ssr-entry.ts', }, + expectSuccess: true, fileAssertions: [ { description: 'SSR output contains encrypted blob (varlock:v1: prefix)', From bdffabefb1ba28edd27567e5580ea9139269269a Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 1 Jun 2026 13:27:16 -0700 Subject: [PATCH 5/8] fix: externalize crypto for Vite SSR to fix encrypted blob builds tsup/esbuild strips the node: prefix from imports, so varlock's built dist uses `import crypto from 'crypto'` instead of 'node:crypto'. Vite SSR only auto-externalizes node:-prefixed imports, so it tries to bundle bare 'crypto' and fails with "Dynamic require not supported". Fix: explicitly add 'crypto' to ssr.external in the Vite plugin config. --- .../cloudflare/cloudflare-shared.ts | 26 + framework-tests/frameworks/expo/expo.test.ts | 25 + framework-tests/harness/pack.ts | 25 +- framework-tests/package.json | 5 +- framework-tests/pnpm-lock.yaml | 1044 +++++++++++++++++ packages/integrations/cloudflare/src/index.ts | 21 +- packages/integrations/cloudflare/src/init.ts | 10 +- .../cloudflare/src/shared-ssr-entry-code.ts | 7 +- .../cloudflare/src/varlock-wrangler.ts | 30 +- .../integrations/expo/src/babel-plugin.ts | 1 + .../integrations/expo/src/metro-config.ts | 1 + .../nextjs/src/turbopack-runtime-inject.ts | 17 +- .../integrations/nextjs/src/webpack-plugin.ts | 13 +- packages/integrations/vite/src/index.ts | 45 +- .../.agents/skills/varlock/SKILL.md | 349 ++++++ packages/varlock-website/astro.config.ts | 1 + packages/varlock-website/skills-lock.json | 11 + .../docs/guides/encrypted-deployments.mdx | 106 ++ .../src/content/docs/integrations/astro.mdx | 4 + .../src/content/docs/integrations/nextjs.mdx | 43 +- .../content/docs/integrations/sveltekit.mdx | 5 +- .../docs/integrations/tanstack-start.mdx | 5 +- .../src/content/docs/integrations/vite.mdx | 49 +- .../docs/reference/root-decorators.mdx | 68 ++ packages/varlock/src/auto-load.ts | 19 +- .../src/cli/commands/generate-key.command.ts | 4 +- .../varlock/src/cli/commands/run.command.ts | 39 +- .../varlock/src/env-graph/lib/decorators.ts | 6 + .../varlock/src/env-graph/lib/env-graph.ts | 6 + packages/varlock/src/runtime/crypto.ts | 8 +- packages/varlock/src/runtime/env.ts | 22 +- skills-lock.json | 11 + 32 files changed, 1870 insertions(+), 156 deletions(-) create mode 100644 framework-tests/pnpm-lock.yaml create mode 100644 packages/varlock-website/.agents/skills/varlock/SKILL.md create mode 100644 packages/varlock-website/skills-lock.json create mode 100644 packages/varlock-website/src/content/docs/guides/encrypted-deployments.mdx create mode 100644 skills-lock.json diff --git a/framework-tests/frameworks/cloudflare/cloudflare-shared.ts b/framework-tests/frameworks/cloudflare/cloudflare-shared.ts index 0beb425f9..2b5ebf1fc 100644 --- a/framework-tests/frameworks/cloudflare/cloudflare-shared.ts +++ b/framework-tests/frameworks/cloudflare/cloudflare-shared.ts @@ -163,6 +163,32 @@ export function defineCloudflareTests( ], }); + cfEnv.describeDevScenario('encrypted env blob with _VARLOCK_ENV_KEY', { + command: `vite dev --port ${basePort + 5}`, + readyPattern: /Local:.*http/, + readyTimeout: 30_000, + env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + templateFiles: { + 'src/index.ts': 'workers/basic-worker.ts', + 'vite.config.ts': 'vite-configs/vite.config.ts', + 'wrangler.jsonc': '_base-wrangler/wrangler.jsonc', + 'tsconfig.json': '_base-wrangler/tsconfig.json', + }, + requests: [ + { + path: '/', + bodyAssertions: { + shouldContain: [ + 'public_var::public-test-value', + 'api_url::https://api.example.com', + 'has_sensitive::yes', + ], + shouldNotContain: ['super-secret-value'], + }, + }, + ], + }); + cfEnv.describeDevScenario('large env (chunking)', { command: `vite dev --port ${basePort + 4}`, readyPattern: /Local:.*http/, diff --git a/framework-tests/frameworks/expo/expo.test.ts b/framework-tests/frameworks/expo/expo.test.ts index 7f2a53f98..a87b452cc 100644 --- a/framework-tests/frameworks/expo/expo.test.ts +++ b/framework-tests/frameworks/expo/expo.test.ts @@ -146,6 +146,31 @@ describe('Expo Integration', () => { }); }); + describe('encrypted env blob', () => { + expoEnv.describeScenario('build succeeds with _VARLOCK_ENV_KEY', { + command: 'node build.mjs', + env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + templateFiles: { + 'app/page.tsx': 'pages/basic-page.tsx', + }, + fileAssertions: [ + { + description: 'public env vars are still statically replaced', + fileGlob: 'dist/**/*.js', + shouldContain: [ + '"Varlock Expo Test"', + '"https://api.example.com"', + ], + }, + { + description: 'sensitive var is NOT inlined', + fileGlob: 'dist/**/*.js', + shouldNotContain: ['super-secret-key-12345'], + }, + ], + }); + }); + describe('invalid schema', () => { expoEnv.describeScenario('build fails on invalid config', { command: 'node build.mjs', diff --git a/framework-tests/harness/pack.ts b/framework-tests/harness/pack.ts index 1718b9cd0..7a0c8d32d 100644 --- a/framework-tests/harness/pack.ts +++ b/framework-tests/harness/pack.ts @@ -64,19 +64,31 @@ const PACKAGE_DIRS: Record = { /** * Finds an existing packed .tgz file for the given package name. */ -function findPackedFile(packageName: string): string | undefined { - if (!existsSync(PACKED_DIR)) return undefined; +function findPackedFiles(packageName: string): Array { + if (!existsSync(PACKED_DIR)) return []; const files = readdirSync(PACKED_DIR).filter((f) => f.endsWith('.tgz')); // Package names like @varlock/nextjs-integration produce tgz files like // varlock-nextjs-integration-0.2.3.tgz (scoped @ and / are stripped/replaced) + // Match "name-VERSION.tgz" where VERSION starts with a digit to avoid + // "varlock-" matching "varlock-vite-integration-..." const normalizedName = packageName .replace(/^@/, '') .replace(/\//g, '-'); - const match = files.find((f) => f.startsWith(`${normalizedName}-`)); - return match ? join(PACKED_DIR, match) : undefined; + return files + .filter((f) => { + if (!f.startsWith(`${normalizedName}-`)) return false; + const rest = f.slice(normalizedName.length + 1); + return /^\d/.test(rest); + }) + .map((f) => join(PACKED_DIR, f)); +} + +function findPackedFile(packageName: string): string | undefined { + const matches = findPackedFiles(packageName); + return matches[0]; } /** @@ -111,9 +123,8 @@ export function packPackages( if (existing) return existing; } - // Remove old tarball if it exists (version may not have changed) - const oldPacked = findPackedFile(name); - if (oldPacked) { + // Remove all old tarballs for this package (handles version bumps leaving stale files) + for (const oldPacked of findPackedFiles(name)) { rmSync(oldPacked); } diff --git a/framework-tests/package.json b/framework-tests/package.json index ff1b518e9..93dcd694c 100644 --- a/framework-tests/package.json +++ b/framework-tests/package.json @@ -19,7 +19,8 @@ }, "devDependencies": { "@types/node": "^24.0.0", - "vitest": "^3.2.4", - "typescript": "^5.9.3" + "@types/react": "19.2.15", + "typescript": "^5.9.3", + "vitest": "^3.2.4" } } diff --git a/framework-tests/pnpm-lock.yaml b/framework-tests/pnpm-lock.yaml new file mode 100644 index 000000000..dcd5e4dbb --- /dev/null +++ b/framework-tests/pnpm-lock.yaml @@ -0,0 +1,1044 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.12.4 + '@types/react': + specifier: 19.2.15 + version: 19.2.15 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.12.4) + +packages: + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.3: + resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/node@24.12.4': + dependencies: + undici-types: 7.16.0 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.3.3(@types/node@24.12.4))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.3(@types/node@24.12.4) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + cac@6.7.14: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.3.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.3: + optional: true + + js-tokens@9.0.1: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + vite-node@3.2.4(@types/node@24.12.4): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.3(@types/node@24.12.4) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.3(@types/node@24.12.4): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.60.4 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 24.12.4 + fsevents: 2.3.3 + + vitest@3.2.4(@types/node@24.12.4): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.3(@types/node@24.12.4)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.16 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.3(@types/node@24.12.4) + vite-node: 3.2.4(@types/node@24.12.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.4 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 5f6f205bd..8bdaa4731 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -8,6 +8,7 @@ import { } from '@varlock/vite-integration'; import { cloudflare, type PluginConfig, type WorkerConfig } from '@cloudflare/vite-plugin'; import { CLOUDFLARE_SSR_ENTRY_CODE } from './shared-ssr-entry-code'; +import { encryptEnvBlobSync, generateEncryptionKeyHex } from 'varlock/encrypt-env'; const isWindows = process.platform === 'win32'; @@ -85,12 +86,24 @@ function formatDevVarsContent( const strValue = typeof item.value === 'string' ? item.value : JSON.stringify(item.value); lines.push(formatEnvLine(key, strValue)); } - // include __VARLOCK_ENV (compact JSON) for the varlock runtime, + // include __VARLOCK_ENV for the varlock runtime, + // encrypt the blob if @encryptInjectedEnv is enabled or _VARLOCK_ENV_KEY is set + const encryptionRequired = !!varlockLoadedEnv?.settings?.encryptInjectedEnv; + let encryptionKey = process.env._VARLOCK_ENV_KEY; + if (encryptionRequired && !encryptionKey) { + // auto-generate a temporary key for dev mode + encryptionKey = generateEncryptionKeyHex(); + } + let envBlob = serializedGraph; + if (encryptionKey) { + envBlob = encryptEnvBlobSync(envBlob, encryptionKey); + lines.push(formatEnvLine('_VARLOCK_ENV_KEY', encryptionKey)); + } // split into chunks if it exceeds CF's 5KB secret limit - if (Buffer.byteLength(serializedGraph) <= CF_SECRET_MAX_BYTES) { - lines.push(formatEnvLine('__VARLOCK_ENV', serializedGraph)); + if (Buffer.byteLength(envBlob) <= CF_SECRET_MAX_BYTES) { + lines.push(formatEnvLine('__VARLOCK_ENV', envBlob)); } else { - const chunks = chunkString(serializedGraph, CF_SECRET_MAX_BYTES); + const chunks = chunkString(envBlob, CF_SECRET_MAX_BYTES); lines.push(formatEnvLine('__VARLOCK_ENV_CHUNKS', String(chunks.length))); for (let i = 0; i < chunks.length; i++) { lines.push(formatEnvLine(`__VARLOCK_ENV_${i}`, chunks[i])); diff --git a/packages/integrations/cloudflare/src/init.ts b/packages/integrations/cloudflare/src/init.ts index 289151f59..170d2e65d 100644 --- a/packages/integrations/cloudflare/src/init.ts +++ b/packages/integrations/cloudflare/src/init.ts @@ -16,6 +16,7 @@ import { env as __cfEnv } from 'cloudflare:workers'; import { initVarlockEnv } from 'varlock/env'; import { patchGlobalConsole } from 'varlock/patch-console'; import { patchGlobalResponse } from 'varlock/patch-response'; +import { isEncryptedBlob, decryptEnvBlobSync } from 'varlock/encrypt-env'; // load __VARLOCK_ENV — may be a single binding or split into chunks if >5KB let __varlockEnvJson: string | undefined; @@ -37,10 +38,13 @@ if (__cfEnv?.__VARLOCK_ENV) { __varlockEnvJson = parts.join(''); } if (__varlockEnvJson) { - try { + // decrypt if the blob was encrypted, then parse and set for initVarlockEnv + if (isEncryptedBlob(__varlockEnvJson)) { + const key = (__cfEnv as any)?._VARLOCK_ENV_KEY as string | undefined; + if (!key) throw new Error('[varlock] __VARLOCK_ENV is encrypted but _VARLOCK_ENV_KEY binding is not set'); + (globalThis as any).__varlockLoadedEnv = JSON.parse(decryptEnvBlobSync(__varlockEnvJson, key)); + } else { (globalThis as any).__varlockLoadedEnv = JSON.parse(__varlockEnvJson); - } catch (err) { - throw new Error(`[varlock] failed to parse __VARLOCK_ENV: ${(err as Error).message}`); } } diff --git a/packages/integrations/cloudflare/src/shared-ssr-entry-code.ts b/packages/integrations/cloudflare/src/shared-ssr-entry-code.ts index d2896aa22..1543e4bfc 100644 --- a/packages/integrations/cloudflare/src/shared-ssr-entry-code.ts +++ b/packages/integrations/cloudflare/src/shared-ssr-entry-code.ts @@ -34,10 +34,11 @@ if (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Work __varlockEnvJson = parts.join(""); } if (__varlockEnvJson) { - try { + if (__varlockEnvJson.startsWith("varlock:v1:")) { + // encrypted blob — stash for decryption by the init module + globalThis.__varlockEncryptedEnv = __varlockEnvJson; + } else { globalThis.__varlockLoadedEnv = JSON.parse(__varlockEnvJson); - } catch (e) { - throw new Error("[varlock] failed to parse __VARLOCK_ENV: " + e.message); } } } diff --git a/packages/integrations/cloudflare/src/varlock-wrangler.ts b/packages/integrations/cloudflare/src/varlock-wrangler.ts index 93ce21d2a..040578f61 100644 --- a/packages/integrations/cloudflare/src/varlock-wrangler.ts +++ b/packages/integrations/cloudflare/src/varlock-wrangler.ts @@ -9,6 +9,7 @@ import { randomBytes } from 'node:crypto'; import { spawn, execSync } from 'node:child_process'; import { execSyncVarlock, VarlockExecError } from 'varlock/exec-sync-varlock'; +import { encryptEnvBlobSync, generateEncryptionKeyHex } from 'varlock/encrypt-env'; const isWindows = process.platform === 'win32'; const debugEnabled = !!process.env.VARLOCK_DEBUG; @@ -54,6 +55,7 @@ function loadSerializedGraph() { graph: JSON.parse(stdout) as { basePath?: string, sources: Array<{ label: string, enabled: boolean, path?: string }>, + settings?: { encryptInjectedEnv?: boolean }, config: Record, }, }; @@ -231,8 +233,19 @@ function formatEnvFileContent(graph: ReturnType) { lines.push(formatEnvLine(key, strValue)); } // include __VARLOCK_ENV for the varlock runtime (compact JSON, no newlines) + // encrypt the blob if @encryptInjectedEnv is enabled or _VARLOCK_ENV_KEY is set + const encryptionRequired = !!graph.graph.settings?.encryptInjectedEnv; + let encryptionKey = process.env._VARLOCK_ENV_KEY; + if (encryptionRequired && !encryptionKey) { + encryptionKey = generateEncryptionKeyHex(); + } + let envBlob = graph.json; + if (encryptionKey) { + envBlob = encryptEnvBlobSync(envBlob, encryptionKey); + lines.push(formatEnvLine('_VARLOCK_ENV_KEY', encryptionKey)); + } // split into chunks if it exceeds CF's 5KB secret limit - addVarlockEnvToLines(lines, graph.json); + addVarlockEnvToLines(lines, envBlob); return lines.join('\n'); } @@ -292,8 +305,21 @@ async function handleDeploy(args: Array) { varFlags.push('--var', `${key}:${strValue}`); } } + // encrypt the blob if @encryptInjectedEnv is enabled or _VARLOCK_ENV_KEY is set + const encryptionRequired = !!loaded.graph.settings?.encryptInjectedEnv; + let encryptionKey = process.env._VARLOCK_ENV_KEY; + if (encryptionRequired && !encryptionKey) { + // auto-generate a key for Cloudflare since we control the deploy pipeline + encryptionKey = generateEncryptionKeyHex(); + console.log('[varlock-wrangler] auto-generated _VARLOCK_ENV_KEY for encrypted deployment'); + } + let envBlob = loaded.json; + if (encryptionKey) { + envBlob = encryptEnvBlobSync(envBlob, encryptionKey); + secretsObj._VARLOCK_ENV_KEY = encryptionKey; + } // split into chunks if it exceeds CF's 5KB secret limit - addVarlockEnvToRecord(secretsObj, loaded.json); + addVarlockEnvToRecord(secretsObj, envBlob); const tmp = createServingTempFile('varlock-secrets'); const content = JSON.stringify(secretsObj); diff --git a/packages/integrations/expo/src/babel-plugin.ts b/packages/integrations/expo/src/babel-plugin.ts index c17423c9d..b2c4fde62 100644 --- a/packages/integrations/expo/src/babel-plugin.ts +++ b/packages/integrations/expo/src/babel-plugin.ts @@ -25,6 +25,7 @@ function loadVarlockConfig() { // Make the loaded env available on globalThis so that any module instance // of varlock/env (including Metro's SSR bundle) can pick it up during // lazy/auto initialization. + // No encryption needed here — Expo's Metro process is both build and runtime. (globalThis as any).__varlockLoadedEnv = varlockLoadedEnv; // initialize varlock and patch globals as necessary diff --git a/packages/integrations/expo/src/metro-config.ts b/packages/integrations/expo/src/metro-config.ts index be755f937..f2dcf2276 100644 --- a/packages/integrations/expo/src/metro-config.ts +++ b/packages/integrations/expo/src/metro-config.ts @@ -80,6 +80,7 @@ export function withVarlockMetroConfig>(config: T) process.env.__VARLOCK_ENV = stdout; const parsed = JSON.parse(stdout) as SerializedEnvGraph; + // No encryption needed here — Expo's Metro process is both build and runtime. (globalThis as any).__varlockLoadedEnv = parsed; initVarlockEnv(); diff --git a/packages/integrations/nextjs/src/turbopack-runtime-inject.ts b/packages/integrations/nextjs/src/turbopack-runtime-inject.ts index 5b680b1b7..91b166824 100644 --- a/packages/integrations/nextjs/src/turbopack-runtime-inject.ts +++ b/packages/integrations/nextjs/src/turbopack-runtime-inject.ts @@ -26,9 +26,22 @@ export function injectVarlockInitIntoTurbopackRuntime(nextDirPath: string) { return; } + const encryptionKey = process.env._VARLOCK_ENV_KEY; + let encryptionRequired = false; + try { + const parsed = JSON.parse(rawEnv); + encryptionRequired = !!parsed?.settings?.encryptInjectedEnv; + } catch { /* ignore parse errors */ } + if (encryptionRequired && !encryptionKey) { + throw new Error( + '[varlock] @encryptInjectedEnv is enabled but _VARLOCK_ENV_KEY is not set.\n' + + 'Generate a key with `varlock generate-key` and set it on your platform.\n' + + 'See https://varlock.dev/guides/encrypted-deployments/ for details.', + ); + } let envPayload = rawEnv; - if (process.env._VARLOCK_ENV_KEY) { - envPayload = encryptEnvBlobSync(rawEnv, process.env._VARLOCK_ENV_KEY); + if (encryptionKey) { + envPayload = encryptEnvBlobSync(rawEnv, encryptionKey); } // Find turbopack runtime files ([turbopack]_runtime.js) and edge-wrapper files. diff --git a/packages/integrations/nextjs/src/webpack-plugin.ts b/packages/integrations/nextjs/src/webpack-plugin.ts index 20b805354..beffe2513 100644 --- a/packages/integrations/nextjs/src/webpack-plugin.ts +++ b/packages/integrations/nextjs/src/webpack-plugin.ts @@ -174,9 +174,18 @@ export function createWebpackConfigFn( // inline the resolved env so it's baked into the build // this removes the need for a .env.production.local file on platforms like Vercel const rawEnv = process.env.__VARLOCK_ENV; + const encryptionRequired = latestLoadedVarlockEnv?.settings?.encryptInjectedEnv; + const encryptionKey = process.env._VARLOCK_ENV_KEY; + if (encryptionRequired && !encryptionKey) { + throw new Error( + '[varlock] @encryptInjectedEnv is enabled but _VARLOCK_ENV_KEY is not set.\n' + + 'Generate a key with `varlock generate-key` and set it on your platform.\n' + + 'See https://varlock.dev/guides/encrypted-deployments/ for details.', + ); + } let envPayload = rawEnv; - if (rawEnv && process.env._VARLOCK_ENV_KEY) { - envPayload = encryptEnvBlobSync(rawEnv, process.env._VARLOCK_ENV_KEY); + if (rawEnv && encryptionKey) { + envPayload = encryptEnvBlobSync(rawEnv, encryptionKey); } const envInline = envPayload ? `process.env.__VARLOCK_ENV = process.env.__VARLOCK_ENV || ${JSON.stringify(envPayload)};` diff --git a/packages/integrations/vite/src/index.ts b/packages/integrations/vite/src/index.ts index efcca568d..fe5153b48 100644 --- a/packages/integrations/vite/src/index.ts +++ b/packages/integrations/vite/src/index.ts @@ -10,7 +10,7 @@ import { patchGlobalServerResponse } from 'varlock/patch-server-response'; import { patchGlobalResponse } from 'varlock/patch-response'; import { createDebug, type SerializedEnvGraph } from 'varlock'; import { execSyncVarlock, VarlockExecError } from 'varlock/exec-sync-varlock'; -import { encryptEnvBlobSync } from 'varlock/encrypt-env'; +import { encryptEnvBlobSync, generateEncryptionKeyHex } from 'varlock/encrypt-env'; import { createReplacerTransformFn, SUPPORTED_FILES } from './transform'; @@ -167,14 +167,30 @@ export function varlockVitePlugin( 'globalThis.__varlockThrowOnMissingKeys = true;', ]; + const encryptionRequired = varlockLoadedEnv?.settings?.encryptInjectedEnv; + let encryptionKey: string | undefined = process.env._VARLOCK_ENV_KEY; + if (ssrInjectMode === 'auto-load') { lines.push("import 'varlock/auto-load';"); } else { if (ssrInjectMode === 'resolved-env') { + if (encryptionRequired && !encryptionKey) { + if (isDevCommand) { + // auto-generate a temporary key for local dev + encryptionKey = generateEncryptionKeyHex(); + process.env._VARLOCK_ENV_KEY = encryptionKey; + } else { + throw new Error( + '[varlock] @encryptInjectedEnv is enabled but _VARLOCK_ENV_KEY is not set.\n' + + 'Generate a key with `varlock generate-key` and set it on your platform.\n' + + 'See https://varlock.dev/guides/encrypted-deployments/ for details.', + ); + } + } const serialized = JSON.stringify(varlockLoadedEnv); - if (process.env._VARLOCK_ENV_KEY) { - const encrypted = encryptEnvBlobSync(serialized, process.env._VARLOCK_ENV_KEY); - lines.push(`globalThis.__varlockLoadedEnv = ${JSON.stringify(encrypted)};`); + if (encryptionKey) { + const encrypted = encryptEnvBlobSync(serialized, encryptionKey); + lines.push(`globalThis.__varlockEncryptedEnv = ${JSON.stringify(encrypted)};`); } else { lines.push(`globalThis.__varlockLoadedEnv = ${JSON.stringify(varlockLoadedEnv)};`); } @@ -185,11 +201,23 @@ export function varlockVitePlugin( lines.push(...vitePluginOptions.ssrEntryCode); } + // decrypt the encrypted env blob before initVarlockEnv runs lines.push( "import { initVarlockEnv } from 'varlock/env';", "import { patchGlobalConsole } from 'varlock/patch-console';", "import { patchGlobalResponse } from 'varlock/patch-response';", ); + // always include decryption support — the blob may be encrypted at build time + // (via _VARLOCK_ENV_KEY) or at deploy time (e.g., Cloudflare varlock-wrangler) + lines.push( + "import { decryptEnvBlobSync } from 'varlock/encrypt-env';", + 'if (globalThis.__varlockEncryptedEnv) {', + ' const __key = typeof process !== \'undefined\' && process.env._VARLOCK_ENV_KEY;', + " if (!__key) throw new Error('[varlock] encrypted env blob present but _VARLOCK_ENV_KEY is not set');", + ' globalThis.__varlockLoadedEnv = JSON.parse(decryptEnvBlobSync(globalThis.__varlockEncryptedEnv, __key));', + ' delete globalThis.__varlockEncryptedEnv;', + '}', + ); if (!isEdgeRuntime) { lines.push("import { patchGlobalServerResponse } from 'varlock/patch-server-response';"); } @@ -266,6 +294,15 @@ See https://varlock.dev/integrations/vite/ for more details. // we do not want to inject via config.define - instead we use @rollup/plugin-replace + // Ensure node:crypto (and its bare alias) is externalized in SSR builds. + // tsup/esbuild strips the node: prefix, so the built varlock dist uses + // `import crypto from 'crypto'` which Vite doesn't auto-externalize. + config.ssr ||= {}; + config.ssr.external ||= []; + if (Array.isArray(config.ssr.external) && !config.ssr.external.includes('crypto')) { + config.ssr.external.push('crypto'); + } + if (!configIsValid) { if (isDevCommand) { // adjust vite's setting so it doesnt bury the error messages diff --git a/packages/varlock-website/.agents/skills/varlock/SKILL.md b/packages/varlock-website/.agents/skills/varlock/SKILL.md new file mode 100644 index 000000000..72577a6b9 --- /dev/null +++ b/packages/varlock-website/.agents/skills/varlock/SKILL.md @@ -0,0 +1,349 @@ +--- +name: varlock +description: >- + Secure environment variable management with Varlock. + Use when handling secrets, API keys, credentials, or any sensitive configuration. + Ensures secrets are never exposed in terminal, logs, or LLM context. + + Provides guidance around integrating varlock into a project, reading/editing .env.schema and other .env files, using the varlock CLI, adding plugins and framework integrations. + + Trigger phrases include "environment variable", "env var", "secrets", ".env", "API key", "credentials", "sensitive", "varlock" +--- + +# Varlock + +This skill helps securely manage env vars and secrets in your project using varlock. + +> **Docs**: https://varlock.dev +> **Repo**: https://github.com/dmno-dev/varlock + +Varlock uses `.env.schema` (instead of `.env.example`) to provide a single source of truth for your project's env vars. Schema info is expressed using `@decorator` style comments. Sensitive values can be set in git-ignored `.env.local` files, passed in via the environment, or use functions to load from secure backends like 1Password, Vault, AWS, etc. + +Basic `.env.schema` example: +```env-spec +# @defaultSensitive=false @defaultRequired=infer +# @currentEnv=$APP_ENV +# @generateTypes(lang=ts, path=env.d.ts) +# --- + +# @type=enum(dev, staging, prod) +APP_ENV=dev + +# @type=url +API_URL=https://api.example.com + +# Description of this var +# @sensitive @required @type=string(startsWith=sk-) +# @docs(https://xyzapi.com/docs/auth) +XYZ_API_KEY= +``` + +Your `.env.schema` is committed to version control and safe for agents to read and update. The `varlock` CLI helps load and validate env vars while masking anything sensitive, and can securely inject env vars into commands. + +**NOTE:** If varlock is installed locally via `package.json` (not as a standalone binary), invoke it via your package manager — e.g., `pnpm exec varlock load`, `bunx varlock load`, `npm exec varlock load`. Check the project's package manager before running CLI commands. + +## CRITICAL: Security rules + +These rules are non-negotiable: + +### Do not expose secrets + +```bash +# NEVER do these - exposes secrets to agent context +cat .env +cat .env.local +echo $SECRET_KEY +printenv | grep API + +# SAFE alternatives +varlock load --agent # JSON output, sensitive values redacted +varlock load # human-readable, sensitive values masked +cat .env.schema # schema only, no secret values +``` + +If the user needs to see a sensitive value, tell them to run `varlock reveal VAR_NAME`. + +### File access rules + +- **Safe to read and edit:** `.env.schema` and any other git-committed `.env` files (usually env-specific files like `.env.development`) +- **Do not read or edit:** `.env`, `.env.local`, `.env.[env].local`, or other gitignored value/override files — these may contain unencrypted secrets +- **Do not log or quote** raw secret values in code, comments, or chat + +### Sensitivity rules + +- Items marked `@sensitive` must not have that decorator removed without confirming with the user +- Ask the user to edit secret values in their local/gitignored env files or their secret provider (1Password, AWS, etc.) — never fill in secrets yourself + +### When the user asks to "show me the .env file" + +Do not read `.env` or `.env.local` directly. Instead run `varlock load` to show masked values, or read `.env.schema` to show the schema. Explain that reading env files directly could expose secrets. + +### When the user asks to "update/set a secret" + +Do not write secret values yourself. Tell the user to either: +1. Update it in their secret provider (1Password, AWS, etc.) and then help them wire it up +2. Edit the value in their `.env.local` file manually + - ideally encrypt it by using `varlock(prompt)` as the value, then run `varlock load` to be prompted + +Then run `varlock load --agent` to validate. + +## File roles + +| File | Role | Agent may edit? | +|------|------|-----------------| +| `.env.schema` | Schema, defaults, decorators, descriptions | Yes | +| `.env.[env]` | Environment-specific tracked config (e.g. `.env.production`) | Yes | +| `.env`, `.env.local` | Local/gitignored values and overrides | No — tell user to edit | +| `.env.[env].local` | Environment-specific local overrides (gitignored) | No — tell user to edit | +| `.env.example` | Legacy example file; migrate into schema | Review with user | + +Ensure `.env.schema` and tracked env-specific files are not gitignored (`!.env.schema`, `!.env.production`, etc. in `.gitignore` if needed). + +### Environment-specific files and precedence + +When `@currentEnv` is set in `.env.schema` (e.g., `@currentEnv=$APP_ENV`), varlock automatically loads matching environment-specific files. Files are applied in increasing precedence order: + +`.env.schema` < `.env` < `.env.local` < `.env.[currentEnv]` < `.env.[currentEnv].local` < `process.env` + +For example, if `APP_ENV=staging`, then `.env.staging` and `.env.staging.local` will be loaded automatically if they exist. A value in `.env.local` overrides one in `.env.schema`, and `process.env` always wins. + +## Schema syntax + +### Root decorators (file header) + +Root decorators go in comment blocks at the top of the file, before the first item. A `# ---` divider usually separates the header from items. + +| Decorator | Purpose | Default | +|-----------|---------|---------| +| `@currentEnv=$VAR` | Sets which item determines the active environment | — | +| `@defaultRequired=bool\|infer` | Default required state for items in this file | `infer` | +| `@defaultSensitive=bool\|inferFromPrefix(PREFIX)` | Default sensitive state for items in this file | `true` | +| `@generateTypes(lang=ts, path=./env.d.ts)` | Auto-generate typed env declarations | — | +| `@import(path, ...keys?)` | Import schema/values from another .env file or directory | — | +| `@plugin(@varlock/name-plugin)` | Load a plugin | — | +| `@setValuesBulk(resolver)` | Inject multiple values from an external source | — | +| `@disable` | Disable loading this file (can use `=forEnv(test)`) | `false` | + +- `@defaultSensitive` defaults to `true` — all items are sensitive unless explicitly marked `@public` or `@sensitive=false`. Set `@defaultSensitive=false` to flip the default. +- `@defaultRequired=infer` (the default): items with a value in the schema are required, items without are optional +- `@defaultSensitive=inferFromPrefix(PUBLIC_)`: items with keys starting with `PUBLIC_` are not sensitive, all others are +- `@import()` accepts `enabled=expr` for conditional imports and `allowMissing=true` for optional imports + +### Item decorators + +Decorators in comment lines directly preceding a config item are attached to that item. A blank line breaks the association. + +| Decorator | Purpose | +|-----------|---------| +| `@required` / `@optional` | Override default required state | +| `@sensitive` / `@public` | Override default sensitive state | +| `@type=dataType` | Set validation/coercion type | +| `@example="value"` | Example value (for docs, not used at runtime) | +| `@docs(url)` or `@docs(label, url)` | Link to related documentation (can be used multiple times) | +| `@icon=collection:name` | Iconify icon ID for generated docs | +| `@auditIgnore` | Suppress "unused in code" warning from `varlock audit` | + +Decorator values can use resolver functions: `@required=forEnv(prod)`, `@sensitive=not(forEnv(dev))`. + +### Common data types (`@type=`) + +`string(startsWith=X)`, `string(matches=/regex/)`, `number`, `boolean`, `url`, `email`, `port`, `enum(a, b, c)`, `ipAddress`, `semver` + +Plain `string` is the default — do not add `@type=string`, just omit `@type` entirely. Only use `@type` when you need a specific type or string constraints. See https://varlock.dev/reference/data-types/ + +### Resolver functions (values) + +Instead of static values, items can use resolver functions: + +```env-spec +# Reference another item ($VAR and ${VAR} are shorthand for ref(VAR)) +FULL_URL=${API_URL}/v2/users + +# Execute a CLI command +SECRET=exec(`op read "op://vault/item/field"`) + +# Conditional logic +API_URL=if(eq($APP_ENV, prod), https://api.example.com, http://localhost:3000) + +# First non-empty value +FALLBACK_VAR=fallback($PRIMARY, $SECONDARY, "default") + +# Map one value to another +APP_ENV=remap($CI_BRANCH, "main", production, /.*/, preview, undefined, development) + +# Check environment (based on @currentEnv) +# @required=forEnv(prod, staging) +PROD_ONLY_KEY= +``` + +Key functions: `ref()`, `concat()`, `exec()`, `fallback()`, `if()`, `eq()`, `not()`, `isEmpty()`, `ifs()`, `remap()`, `forEnv()` + +See https://varlock.dev/reference/functions/ + +## Setting sensitive values + +There are two main approaches — they can be used together. + +### Approach 1: Plugins (version-controlled secret references) + +Varlock plugins let you declaratively reference secrets from external providers directly in your `.env.schema`. The references are safe to commit — actual values are fetched at load time. + +```env-spec title=".env.schema" +# @plugin(@varlock/1password-plugin) +# @initOp(token=$OP_TOKEN, allowAppAuth=forEnv(dev)) +# --- +# @sensitive @type=opServiceAccountToken +OP_TOKEN= +# @sensitive +MY_SECRET=op(op://my-vault/item-name/field-name) +``` + +Each plugin provides its own resolver functions (e.g., `op()` for 1Password, `awsSecret()` for AWS). See [Plugins](#plugins) below for the full list and https://varlock.dev/guides/plugins/ for setup details. + +### Approach 2: Local encryption with `varlock()` (git-ignored files) + +For secrets stored locally in git-ignored files like `.env.local`, use the `varlock()` function for device-local encryption so nothing is stored in plaintext: + +```env-spec title=".env.local" +# Encrypted value — decrypted automatically at load time +API_KEY=varlock("local:") + +# Prompt mode — on next `varlock load`, user is prompted to enter the value +# which is encrypted and written back to this file automatically +NEW_SECRET=varlock(prompt) +``` + +**How to encrypt values:** +- **Interactive prompt:** Set the value to `varlock(prompt)` and run `varlock load` — the user will be prompted securely, and the encrypted value replaces the placeholder automatically +- **Encrypt in bulk:** `varlock encrypt --file .env.local` encrypts all sensitive plaintext values in-place +- **Encrypt a single value:** `varlock encrypt` prompts for a value and prints the encrypted result to copy/paste +- **Pipe via stdin:** To encrypt a value without exposing it in your context (e.g., a generated key or a value read from another tool), pipe it into `varlock encrypt`: + ```bash + some-cli-that-outputs-secret | varlock encrypt + # prints: SOME_SENSITIVE_KEY=varlock("local:") + ``` + This keeps the plaintext secret out of shell history and agent context. + +Encryption is hardware-backed on macOS (Secure Enclave + Touch ID), Windows (DPAPI + Windows Hello), and Linux (TPM2), with a file-based fallback on all platforms. On macOS, `keychain()` is also available as a built-in alternative that stores values in the system keychain. + +See https://varlock.dev/guides/local-encryption/ + +## Organization + +Ask the user how their repo is structured before designing the env layout. + +**Single project:** one `.env.schema` at the repo root is usually enough. + +**Monorepo / multi-app:** use `@import()` to share common config: + +```env-spec title="apps/web/.env.schema" +# Import shared config from root +# @import(../../.env.schema) +# Import from a sibling service (specific keys only) +# @import(../api/.env.schema, SHARED_API_URL, SHARED_DB_HOST) +# --- +APP_PUBLIC_URL=http://localhost:3000 +``` + +- **Root schema** — shared service URLs, org-wide defaults, common keys +- **Per-app schemas** — app-specific items, importing what they need from root/siblings +- Keep imports explicit; avoid circular imports + +Discuss with the user: which values belong at the root vs per-package, which environments they use. + +See https://varlock.dev/guides/import/ + +## Plugins + +Plugins add resolver functions, data types, and decorators for external secret providers. Install with `@plugin()` in your `.env.schema`: + +```env-spec +# @plugin(@varlock/1password-plugin) +``` + +In JS projects, also install the npm package. With the standalone binary, pin a version: `@plugin(@varlock/1password-plugin@1.2.3)`. + +**Available plugins:** 1Password, AWS Secrets Manager, Azure Key Vault, Bitwarden, Dashlane, Doppler, Google Secret Manager, HashiCorp Vault, Infisical, Akeyless, KeePass, Keeper, Passbolt, Proton Pass, Pass, macOS Keychain (built-in). + +See https://varlock.dev/plugins/overview/ for setup details for each plugin. + +## Integrations (frameworks / runtimes) + +Pick the official integration for the project's framework — do not guess. Check https://varlock.dev/integrations/overview/ for the specific guide (Next.js, Vite, Astro, SvelteKit, Bun, Cloudflare, Expo, etc.). + +Typical steps: +1. Confirm `varlock` is installed (`varlock init --agent` or existing dependency) +2. Follow the integration guide for build/dev wiring, generated types, and any required config +3. Prefer the integration's recommended entry point (`varlock/auto-load`, Vite plugin, etc.) over ad-hoc `process.env` usage + +When a framework integration is active, it handles loading and injecting env vars automatically — `varlock run` is **not needed** for the framework's own dev/build commands. Only use `varlock run -- ` for other scripts or tools that the integration doesn't cover (e.g., one-off migrations, CLI tools, non-JS commands). + +**Migrating from dotenv:** replace `dotenv/config` or `dotenvx run` with the varlock equivalent — see https://varlock.dev/guides/migrate-from-dotenv/ + +**Non-JS apps/services:** use `varlock run` or pipe `varlock load --format shell` — see https://varlock.dev/integrations/other-languages/ + +## Setup + +**Installing varlock:** +- **JS projects:** Install as a dev dependency — `npm install -D varlock` (or `bun add -D varlock`, `pnpm add -D varlock`) +- **Standalone binary (non-JS or global use):** See https://varlock.dev/getting-started/installation/ + +**Getting started:** +1. Run `varlock init --agent` to auto-generate an initial `.env.schema` from existing `.env` / `.env.example` files +2. Review the generated schema with the user — init heuristics are a draft, not final +3. Optionally install this skill: + - **skills** (recommended): `npx skills add dmno-dev/varlock` — update with `npx skills update varlock` + - **GitHub CLI** (v2.90+): `gh skill install dmno-dev/varlock varlock` — update with `gh skill update varlock` + +## Schema checklist + +After init or when editing `.env.schema`: + +1. Review auto-generated items — heuristics are not final +2. Add description comments where names are not self-explanatory +3. Set `@type` only when not a plain string (omit `@type=string`) +4. Mark `@required` / `@optional` as needed (or adjust root `@defaultRequired`) +5. Confirm `@sensitive` on secrets, keys, tokens, and credentials with the user +6. Move useful values to `@example`; delete dummy placeholders +7. Add `@docs()` links where helpful +8. Remove redundant values from other `.env` files after defaults move into the schema + +## Validation loop + +After schema changes: + +```bash +varlock load --agent +``` + +Fix schema and tracked env files based on validation errors. Do not patch gitignored `.local` value files to silence schema errors — ask the user to update secrets locally. + +## CLI quick reference + +Run `varlock --help` or `varlock --help` for full flags and options. + +| Command | Use when | +|---------|----------| +| `varlock init --agent` | Setting up varlock non-interactively | +| `varlock load --agent` | Validating config safely (JSON, sensitive values redacted) | +| `varlock load` | Showing human-readable validation to the user | +| `varlock run -- ` | Injecting resolved env into a process | +| `varlock printenv VAR_NAME` | Print a single resolved env var to stdout | +| `varlock reveal` | Securely view/copy a sensitive value | +| `varlock encrypt` | Encrypt values (single or `--file` for bulk) | +| `varlock scan` | Scan files for leaked secrets (`--staged` for pre-commit, `--install-hook` to set up) | +| `varlock audit` | Detect drift between schema and code usage | +| `varlock typegen` | Explicitly trigger type generation from schema (usually triggered automatically) | +| `varlock lock` | Lock biometric session (requires re-auth on next decrypt) | + +## Advanced + +- Multiple environments: https://varlock.dev/guides/environments/ +- Split large schemas with `@import`: https://varlock.dev/guides/import/ +- Device-local encryption: https://varlock.dev/guides/local-encryption/ +- `package.json` config (`varlock.loadPath`): https://varlock.dev/reference/cli-commands/ +- Built-in variables (`$VARLOCK_ENV` for auto-detecting environment): https://varlock.dev/reference/builtin-variables/ + +## Docs + +For details beyond this skill, use the Varlock Docs MCP tool if installed in your AI tool, or refer to https://varlock.dev/guides/schema as a starting point. diff --git a/packages/varlock-website/astro.config.ts b/packages/varlock-website/astro.config.ts index 7b7c803d2..bb5e5aa9e 100644 --- a/packages/varlock-website/astro.config.ts +++ b/packages/varlock-website/astro.config.ts @@ -147,6 +147,7 @@ export default defineConfig({ { label: 'Schema', slug: 'guides/schema' }, { label: 'Secrets', slug: 'guides/secrets' }, { label: 'Local encryption', slug: 'guides/local-encryption' }, + { label: 'Encrypted deployments', slug: 'guides/encrypted-deployments', badge: 'new' }, { label: 'OIDC Workload Identity', slug: 'guides/oidc', badge: 'new' }, { label: 'Environments', slug: 'guides/environments' }, { label: 'Imports', slug: 'guides/import' }, diff --git a/packages/varlock-website/skills-lock.json b/packages/varlock-website/skills-lock.json new file mode 100644 index 000000000..62251841e --- /dev/null +++ b/packages/varlock-website/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "varlock": { + "source": "dmno-dev/varlock", + "sourceType": "github", + "skillPath": "skills/varlock/SKILL.md", + "computedHash": "6d538298c7d96aeb1a69b9ec8a535ba6597ed9bfbaf23cc749d0505758d098a9" + } + } +} diff --git a/packages/varlock-website/src/content/docs/guides/encrypted-deployments.mdx b/packages/varlock-website/src/content/docs/guides/encrypted-deployments.mdx new file mode 100644 index 000000000..d0b9eca40 --- /dev/null +++ b/packages/varlock-website/src/content/docs/guides/encrypted-deployments.mdx @@ -0,0 +1,106 @@ +--- +title: Encrypted deployments +description: Encrypt the env blob in your build output so secrets are never stored in plaintext +--- +import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; +import ExecCommandWidget from '@/components/ExecCommandWidget.astro'; + +When deploying SSR applications to **serverless platforms** (like Vercel, Netlify, or Cloudflare Workers), varlock injects the fully resolved env data into your server-side build output so it's available at runtime without needing the CLI or filesystem access. This is necessary because serverless environments don't give you control over how the application boots. + +By default, this blob is **plaintext JSON**. Since it only appears in server-side code, it's generally safe — your secrets are not exposed to the client. However, encrypting the blob provides an extra layer of protection, particularly against secrets leaking via **sourcemaps** that may be uploaded to error tracking services. + +## Enabling encryption + +Add the [`@encryptInjectedEnv`](/reference/root-decorators/#encryptinjectedenv) root decorator to your `.env.schema`: + +```env-spec title=".env.schema" +# @encryptInjectedEnv +# --- +SECRET_KEY= # @sensitive +``` + +You can also enable it conditionally for specific environments: + +```env-spec title=".env.schema" +# @encryptInjectedEnv=forEnv(prod) +# --- +SECRET_KEY= # @sensitive +``` + +## How it works + +1. At **build time**, varlock encrypts the serialized env graph with **AES-256-GCM** before injecting it +2. The encrypted blob is prefixed with `varlock:v1:` — you can verify encryption by inspecting your build output +3. At **runtime**, `initVarlockEnv()` detects the encrypted prefix and decrypts using `_VARLOCK_ENV_KEY` from the runtime environment +4. The key is **never baked into the build** — it must always come from the runtime environment + +## Key management + +The encryption key (`_VARLOCK_ENV_KEY`) is managed differently depending on your platform: + +### Local development + +No setup needed — varlock **auto-generates a temporary key** when running dev servers. The key exists only in memory for the duration of the dev session. + +### Cloudflare Workers + +No setup needed — `varlock-wrangler deploy` **auto-generates a key** and uploads it alongside your encrypted env as a Cloudflare secret binding. The key persists across deploys. + +### Vercel, Netlify, and other platforms + +You must set `_VARLOCK_ENV_KEY` as an environment variable on your platform. The key must be available at both **build time** (for encryption) and **runtime** (for decryption). + + + +1. **Generate and set the key** + + + + ```bash + varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY production preview development --sensitive + ``` + + + ```bash + netlify env:set _VARLOCK_ENV_KEY $(varlock generate-key --plain) --secret + ``` + + + Generate a key and set it as a secret/env var on your platform: + + + + + +2. **Deploy your app** + + The build will fail with a clear error if `@encryptInjectedEnv` is enabled but `_VARLOCK_ENV_KEY` is not set. + + + +## Supported integrations + +| Integration | How it works | +|---|---| +| **[Next.js](/integrations/nextjs/)** | Encrypts the env blob injected into the webpack/turbopack build output | +| **[Vite](/integrations/vite/)** | Encrypts the env blob when using `ssrInjectMode: 'resolved-env'` | +| **[Astro](/integrations/astro/)** | Uses the Vite integration — encryption applies to SSR adapter builds | +| **[SvelteKit](/integrations/sveltekit/)** | Uses the Vite integration — encryption applies to SSR builds | +| **[TanStack Start](/integrations/tanstack-start/)** | Uses the Vite integration — encryption applies to SSR builds | +| **[Cloudflare Workers](/integrations/cloudflare/)** | Encrypts the `__VARLOCK_ENV` secret binding uploaded at deploy time | +| **[Expo](/integrations/expo/)** | Encrypts the env blob used by the Metro bundler for server routes | + +## Verifying encryption + +After building with encryption enabled, you can inspect your build output to confirm the blob is encrypted: + +```bash +# Look for the encrypted prefix in build output +grep -r 'varlock:v1:' dist/ .next/server/ 2>/dev/null +``` + +If encryption is working, you'll see `varlock:v1:` followed by a base64-encoded ciphertext instead of raw JSON. + +## Alternative: manual key without the decorator + +You can also enable encryption without the `@encryptInjectedEnv` decorator by simply setting `_VARLOCK_ENV_KEY` in your environment. When present, varlock will encrypt the blob — but it won't enforce the key's presence or auto-generate it. diff --git a/packages/varlock-website/src/content/docs/integrations/astro.mdx b/packages/varlock-website/src/content/docs/integrations/astro.mdx index 95269ee7d..d5c5705ea 100644 --- a/packages/varlock-website/src/content/docs/integrations/astro.mdx +++ b/packages/varlock-website/src/content/docs/integrations/astro.mdx @@ -163,6 +163,10 @@ This integration will automatically inject a new middleware that scans outgoing +:::note[Encrypting the env blob] +When deploying SSR Astro apps to serverless platforms, varlock injects the resolved env into your server-side build output as plaintext JSON. This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. +::: + --- ## Reference diff --git a/packages/varlock-website/src/content/docs/integrations/nextjs.mdx b/packages/varlock-website/src/content/docs/integrations/nextjs.mdx index 0ab7cbbe0..9605e81ba 100644 --- a/packages/varlock-website/src/content/docs/integrations/nextjs.mdx +++ b/packages/varlock-website/src/content/docs/integrations/nextjs.mdx @@ -327,47 +327,8 @@ varlock run -- node .next/standalone/server.js --- -## Encrypting the env blob ||encrypting-the-env-blob|| - -When deploying to platforms like Vercel, varlock injects the fully resolved env data into your build output so it's available at runtime without needing the CLI or filesystem access. By default, this blob is plaintext JSON — meaning anyone with access to the build artifact can read your secrets. - -To encrypt this blob, set the `_VARLOCK_ENV_KEY` environment variable with a 256-bit hex key. When present at build time, the blob is encrypted with AES-256-GCM before being injected. At runtime, the init bundle decrypts it using the same key from the runtime environment. - - - -1. **Generate and set the key on Vercel** - - This one-liner generates a key and sets it as a sensitive env var on Vercel for all environments: - - ```bash - varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY production preview development --sensitive - ``` - - Or generate and set it manually on your platform — the key must be available at both **build time** (for encryption) and **runtime** (for decryption): - - - -3. **Optionally add it to your `.env.local` for local builds** - - If you want local builds to also encrypt the blob (e.g., to test the flow), add the key to `.env.local`: - - ```env title=".env.local" - _VARLOCK_ENV_KEY=your-64-char-hex-key-here - ``` - - - -:::tip -You can define `_VARLOCK_ENV_KEY` in your `.env.schema` to enable validation (e.g., required in production). It will automatically be excluded from the injected blob and from type generation — it's infrastructure, not application config. - -```env-spec title=".env.schema" -# @sensitive -_VARLOCK_ENV_KEY= -``` -::: - -:::note -The encryption key is **never baked into the build**. It must always come from the runtime environment (e.g., a Vercel environment variable). The encrypted blob is useless without it. +:::note[Encrypting the env blob] +When deploying to serverless platforms like Vercel, varlock injects the resolved env into your server-side build output as plaintext JSON. This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. ::: --- diff --git a/packages/varlock-website/src/content/docs/integrations/sveltekit.mdx b/packages/varlock-website/src/content/docs/integrations/sveltekit.mdx index 30445cd21..3e1af932c 100644 --- a/packages/varlock-website/src/content/docs/integrations/sveltekit.mdx +++ b/packages/varlock-website/src/content/docs/integrations/sveltekit.mdx @@ -194,5 +194,6 @@ PUBLIC_API_URL= # non-sensitive (has PUBLIC_ prefix) All non-sensitive items are replaced at build time wherever `ENV.FOO` appears. Sensitive items are only available in server contexts — build-time checks block them from client bundles, and runtime log redaction / response leak detection catch any that slip past the build layer. ::: - - +:::note[Encrypting the env blob] +When deploying SSR SvelteKit apps to serverless platforms, varlock injects the resolved env into your server-side build output as plaintext JSON. This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. +::: diff --git a/packages/varlock-website/src/content/docs/integrations/tanstack-start.mdx b/packages/varlock-website/src/content/docs/integrations/tanstack-start.mdx index 515d38645..685446717 100644 --- a/packages/varlock-website/src/content/docs/integrations/tanstack-start.mdx +++ b/packages/varlock-website/src/content/docs/integrations/tanstack-start.mdx @@ -136,5 +136,6 @@ TanStack Start inherits [Vite's env var approach](https://tanstack.com/start/v0/ With varlock, your `.env.schema` is the single source of truth — it handles validation, type coercion, TypeScript generation, sensitivity controls, and more, replacing the need for separate type declarations, Zod schemas, and the `VITE_` prefix convention. You also get leak detection (sensitive values are automatically scrubbed from outgoing HTTP responses) and clear error messages when env vars are missing or invalid — no more silent `undefined` values at runtime. See the [Vite integration page](/integrations/vite/) for the full details. - - +:::note[Encrypting the env blob] +When deploying SSR TanStack Start apps to serverless platforms, varlock injects the resolved env into your server-side build output as plaintext JSON. This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. +::: diff --git a/packages/varlock-website/src/content/docs/integrations/vite.mdx b/packages/varlock-website/src/content/docs/integrations/vite.mdx index 56186699e..07318583f 100644 --- a/packages/varlock-website/src/content/docs/integrations/vite.mdx +++ b/packages/varlock-website/src/content/docs/integrations/vite.mdx @@ -243,53 +243,8 @@ All non-sensitive items are bundled at build time via `ENV`, while `import.meta. --- -## Encrypting the env blob ||encrypting-the-env-blob|| - -When using `ssrInjectMode: 'resolved-env'`, varlock injects the fully resolved env data into your SSR build output. By default, this blob is plaintext JSON — meaning anyone with access to the build artifact can read your secrets. - -To encrypt this blob, set the `_VARLOCK_ENV_KEY` environment variable with a 256-bit hex key. When present at build time, the blob is encrypted with AES-256-GCM before being injected. At runtime, it's decrypted using the same key from the runtime environment. - - - -1. **Generate and set the key on your platform** - - For Vercel, this one-liner generates a key and sets it as a sensitive env var for all environments: - - ```bash - varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY production preview development --sensitive - ``` - - Or generate and set it manually — the key must be available at both **build time** (for encryption) and **runtime** (for decryption): - - - -2. **Ensure you're using `resolved-env` mode** - - Encryption only applies when the blob is inlined into the build: - - ```ts title="vite.config.ts" - varlockVitePlugin({ ssrInjectMode: 'resolved-env' }) - ``` - -3. **Optionally add it to your `.env.local` for local builds** - - ```env title=".env.local" - _VARLOCK_ENV_KEY=your-64-char-hex-key-here - ``` - - - -:::tip -You can define `_VARLOCK_ENV_KEY` in your `.env.schema` to enable validation. It will automatically be excluded from the injected blob and from type generation. - -```env-spec title=".env.schema" -# @sensitive -_VARLOCK_ENV_KEY= -``` -::: - -:::note -The encryption key is **never baked into the build**. It must always come from the runtime environment. The encrypted blob is useless without it. +:::note[Encrypting the env blob] +When deploying to serverless platforms, varlock injects the resolved env into your server-side build output as plaintext JSON (when using `ssrInjectMode: 'resolved-env'`). This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. ::: --- diff --git a/packages/varlock-website/src/content/docs/reference/root-decorators.mdx b/packages/varlock-website/src/content/docs/reference/root-decorators.mdx index 20b980052..4f3534bea 100644 --- a/packages/varlock-website/src/content/docs/reference/root-decorators.mdx +++ b/packages/varlock-website/src/content/docs/reference/root-decorators.mdx @@ -327,6 +327,74 @@ See note on [`@redactLogs`](#redactlogs) about potential performance impact.
+
+### `@encryptInjectedEnv` +**Value type:** `boolean` + +Controls whether the injected env blob in server-side build output is encrypted. When enabled, varlock encrypts the blob with AES-256-GCM using the `_VARLOCK_ENV_KEY` environment variable. + +This is primarily relevant for serverless deployments (Vercel, Netlify, etc.) where varlock injects resolved env data into the build output. The blob only appears in server-side code and is generally safe, but encryption provides extra protection — particularly against secrets leaking via sourcemaps. + +**Options:** +- `false` (default): Blob is injected as plaintext JSON +- `true`: Blob is encrypted, `_VARLOCK_ENV_KEY` required at build time and runtime +- `forEnv(...)`: Conditionally enable based on the current environment + +```env-spec +# @encryptInjectedEnv +# --- +SECRET_KEY= # @sensitive +``` + +```env-spec +# only encrypt in production +# @encryptInjectedEnv=forEnv(prod) +# --- +SECRET_KEY= # @sensitive +``` + +**Key management:** +- For **Cloudflare Workers**, the key is auto-generated and uploaded as a secret binding — no manual setup needed +- For **local dev**, a temporary key is auto-generated when running dev servers +- For **production deploys** to other platforms, you must set `_VARLOCK_ENV_KEY` on your platform — see the [encrypted deployments guide](/guides/encrypted-deployments/) +
+ +
+### `@disableProcessEnvInjection` +**Value type:** `boolean` + +Prevents varlock from injecting resolved values into `process.env`. When enabled, env vars are only accessible via the `ENV` proxy — `process.env.MY_VAR` will not contain varlock-resolved values. + +This is useful for security hardening. Combined with [`@encryptInjectedEnv`](#encryptinjectedenv), it ensures that no plaintext secrets exist in `process.env` at all. + +**Options:** +- `false` (default): Values are injected into `process.env` as usual +- `true`: Values are only accessible via `ENV` +- `forEnv(...)`: Conditionally enable based on the current environment + +```env-spec +# @disableProcessEnvInjection +# --- +SECRET_KEY= # @sensitive +PUBLIC_URL= +``` + +```env-spec +# disable process.env injection only in production +# @disableProcessEnvInjection=forEnv(prod) +# --- +SECRET_KEY= # @sensitive +``` + +:::caution +When enabled, any code that reads from `process.env` directly (including third-party libraries) will not see varlock-resolved values. Make sure all env access in your application goes through the `ENV` proxy. +::: + +:::note +This decorator only affects the runtime `initVarlockEnv()` behavior — it does **not** affect [`varlock run`](/reference/cli-commands/#run), which always injects individual env vars into the child process by default. Use `varlock run --no-inject-vars` if you also want to skip individual var injection when using `varlock run`. +::: +
+
### `@auditIgnorePaths()` **Arg types:** `[ ...paths: string[] ]` diff --git a/packages/varlock/src/auto-load.ts b/packages/varlock/src/auto-load.ts index 4fdd551fd..80545659f 100644 --- a/packages/varlock/src/auto-load.ts +++ b/packages/varlock/src/auto-load.ts @@ -1,4 +1,5 @@ import { execSyncVarlock, VarlockExecError } from './lib/exec-sync-varlock'; +import { encryptEnvBlobSync, generateEncryptionKeyHex } from './runtime/crypto'; import { initVarlockEnv } from './runtime/env'; import { patchGlobalConsole } from './runtime/patch-console'; @@ -18,7 +19,23 @@ try { // rather than from process.cwd(), which may be an unrelated workspace root. callerDir: import.meta.dirname ?? new URL('.', import.meta.url).pathname, }); - process.env.__VARLOCK_ENV = stdout; + + const parsed = JSON.parse(stdout); + // set parsed object on globalThis so initVarlockEnv() picks it up directly + (globalThis as any).__varlockLoadedEnv = parsed; + + // encrypt the blob in process.env so sensitive values aren't sitting + // in plaintext in process.env.__VARLOCK_ENV + let encryptionKey = process.env._VARLOCK_ENV_KEY; + if (parsed.settings?.encryptInjectedEnv && !encryptionKey) { + encryptionKey = generateEncryptionKeyHex(); + process.env._VARLOCK_ENV_KEY = encryptionKey; + } + if (encryptionKey) { + process.env.__VARLOCK_ENV = encryptEnvBlobSync(stdout, encryptionKey); + } else { + process.env.__VARLOCK_ENV = stdout; + } } catch (err) { if (err instanceof VarlockExecError && err.stderr) { process.stderr.write(err.stderr); diff --git a/packages/varlock/src/cli/commands/generate-key.command.ts b/packages/varlock/src/cli/commands/generate-key.command.ts index 13a2fc1a7..d41960778 100644 --- a/packages/varlock/src/cli/commands/generate-key.command.ts +++ b/packages/varlock/src/cli/commands/generate-key.command.ts @@ -1,6 +1,6 @@ +import { randomBytes } from 'node:crypto'; import { define } from 'gunshi'; -import { generateEncryptionKeyHex } from '../../runtime/crypto'; import { type TypedGunshiCommandFn } from '../helpers/gunshi-type-utils'; export const commandSpec = define({ @@ -10,7 +10,7 @@ export const commandSpec = define({ }); export const commandFn: TypedGunshiCommandFn = async () => { - const key = generateEncryptionKeyHex(); + const key = randomBytes(32).toString('hex'); console.log(''); console.log('Generated _VARLOCK_ENV_KEY:'); diff --git a/packages/varlock/src/cli/commands/run.command.ts b/packages/varlock/src/cli/commands/run.command.ts index 140cc99b8..f1ad0aa3f 100644 --- a/packages/varlock/src/cli/commands/run.command.ts +++ b/packages/varlock/src/cli/commands/run.command.ts @@ -5,6 +5,7 @@ import { exec } from '../../lib/exec'; import { loadVarlockEnvGraph } from '../../lib/load-graph'; import { checkForConfigErrors, checkForNoEnvFiles, checkForSchemaErrors } from '../helpers/error-checks'; import { type TypedGunshiCommandFn } from '../helpers/gunshi-type-utils'; +import { CliExitError } from '../helpers/exit-error'; import { resetRedactionMap, redactSensitiveConfig } from '../../runtime/env'; export const commandSpec = define({ @@ -20,9 +21,14 @@ export const commandSpec = define({ type: 'boolean', description: 'Disable stdout/stderr redaction and use stdio inherit for full TTY pass-through (use for interactive tools that require raw TTY)', }, + inject: { + type: 'string', + short: 'i', + description: 'Control what gets injected into the child process env: "all" (default), "vars" (individual vars only, no blob), or "blob" (only __VARLOCK_ENV blob, no individual vars)', + }, 'no-inject-graph': { type: 'boolean', - description: 'Disable injection of __VARLOCK_ENV serialized config graph into the child process environment (prevents sensitive value exposure via env inspection)', + description: 'Deprecated: use --inject vars instead', }, path: { type: 'string', @@ -40,7 +46,8 @@ Examples: varlock run -- python script.py # Run a Python script varlock run -- sh -c 'echo $MY_VAR' # Use shell expansion for env vars varlock run --no-redact-stdout -- psql # Preserve TTY for interactive tools - varlock run --no-inject-graph -- sh # Omit serialized config graph from env + varlock run --inject vars -- sh # Inject only individual vars, no blob + varlock run --inject blob -- node app.js # Inject only the blob, no individual vars varlock run --path .env.prod -- node app.js # Use a specific .env file varlock run --path ./config/ -- node app.js # Use a specific directory varlock run -p ./envs -p ./overrides -- node app.js # Use multiple directories @@ -49,7 +56,8 @@ Examples: 💡 Tip: For shell expansion of env vars, use: sh -c 'your command here' 💡 Tip: Use --no-redact-stdout for interactive tools that require raw TTY (e.g., psql, claude) -💡 Tip: Use --no-inject-graph to prevent __VARLOCK_ENV from being visible in child process environment (e.g., interactive shells, long-lived processes) +💡 Tip: Use --inject vars to prevent __VARLOCK_ENV from being visible in child process env +💡 Tip: Use --inject blob when your app uses the ENV proxy and doesn't need individual process.env vars `.trim(), }); @@ -97,16 +105,33 @@ export const commandFn: TypedGunshiCommandFn = async (ctx) = const serializedGraph = envGraph.getSerializedGraph(); // console.log(resolvedEnv); - const noInjectGraph = ctx.values['no-inject-graph'] ?? false; + // handle deprecated --no-inject-graph flag + let injectDefault = 'all'; + if (ctx.values['no-inject-graph']) { + console.warn('[varlock] ⚠️ --no-inject-graph is deprecated, use --inject vars instead'); + injectDefault = 'vars'; + } + const injectMode = ctx.values.inject ?? injectDefault; + const validModes = ['all', 'vars', 'blob']; + if (!validModes.includes(injectMode)) { + throw new CliExitError(`Invalid --inject mode: "${injectMode}". Must be one of: ${validModes.join(', ')}`); + } + const injectVars = injectMode === 'all' || injectMode === 'vars'; + const injectBlob = injectMode === 'all' || injectMode === 'blob'; - // needs more thought here const fullInjectedEnv: NodeJS.ProcessEnv = { ...process.env, - ...resolvedEnv, + ...(injectVars ? resolvedEnv : {}), __VARLOCK_RUN: '1', // flag for a child process to detect it is running via `varlock run` - ...(!noInjectGraph ? { __VARLOCK_ENV: JSON.stringify(serializedGraph) } : {}), + ...(injectBlob ? { __VARLOCK_ENV: JSON.stringify(serializedGraph) } : {}), }; + // when only injecting the blob, also inject the encryption key so the + // child process can decrypt it (if encrypted) + if (injectBlob && !injectVars && process.env._VARLOCK_ENV_KEY) { + fullInjectedEnv._VARLOCK_ENV_KEY = process.env._VARLOCK_ENV_KEY; + } + const redactLogs = serializedGraph.settings?.redactLogs ?? true; const noRedactStdout = ctx.values['no-redact-stdout'] ?? false; diff --git a/packages/varlock/src/env-graph/lib/decorators.ts b/packages/varlock/src/env-graph/lib/decorators.ts index 27830052b..417589cfd 100644 --- a/packages/varlock/src/env-graph/lib/decorators.ts +++ b/packages/varlock/src/env-graph/lib/decorators.ts @@ -333,6 +333,12 @@ export const builtInRootDecorators: Array> = [ { name: 'preventLeaks', }, + { + name: 'encryptInjectedEnv', + }, + { + name: 'disableProcessEnvInjection', + }, { name: 'auditIgnorePaths', isFunction: true, diff --git a/packages/varlock/src/env-graph/lib/env-graph.ts b/packages/varlock/src/env-graph/lib/env-graph.ts index 346eda17b..47a0ccb63 100644 --- a/packages/varlock/src/env-graph/lib/env-graph.ts +++ b/packages/varlock/src/env-graph/lib/env-graph.ts @@ -46,6 +46,8 @@ export type SerializedEnvGraph = { settings: { redactLogs?: boolean; preventLeaks?: boolean; + encryptInjectedEnv?: boolean; + disableProcessEnvInjection?: boolean; }, config: Record Date: Mon, 1 Jun 2026 13:33:17 -0700 Subject: [PATCH 6/8] fix: externalize bare crypto for Vite SSR builds esbuild strips the node: prefix from imports, so varlock's built dist uses bare 'crypto' which Vite SSR doesn't auto-externalize. Add it to ssr.external in the Vite plugin config. --- packages/integrations/vite/src/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/integrations/vite/src/index.ts b/packages/integrations/vite/src/index.ts index fe5153b48..2bc46118b 100644 --- a/packages/integrations/vite/src/index.ts +++ b/packages/integrations/vite/src/index.ts @@ -294,9 +294,8 @@ See https://varlock.dev/integrations/vite/ for more details. // we do not want to inject via config.define - instead we use @rollup/plugin-replace - // Ensure node:crypto (and its bare alias) is externalized in SSR builds. - // tsup/esbuild strips the node: prefix, so the built varlock dist uses - // `import crypto from 'crypto'` which Vite doesn't auto-externalize. + // esbuild strips the node: prefix from imports, so varlock's built dist + // uses bare 'crypto' which Vite SSR doesn't auto-externalize config.ssr ||= {}; config.ssr.external ||= []; if (Array.isArray(config.ssr.external) && !config.ssr.external.includes('crypto')) { From 25bdc153adfb6307c19a25cbfc7eb80ea8e737b0 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 1 Jun 2026 14:23:51 -0700 Subject: [PATCH 7/8] fix: externalize bare crypto for Vite SSR, skip for Cloudflare esbuild strips the node: prefix from imports, so varlock's built dist uses bare 'crypto' which Vite SSR doesn't auto-externalize. Add it to ssr.external in the Vite plugin, but skip when CF Vite plugin is present since Workers handle node builtins via nodejs_compat. --- bun.lock | 141 +++++++++++++++++++++--- package.json | 1 + packages/integrations/vite/src/index.ts | 15 ++- packages/varlock/package.json | 2 +- 4 files changed, 140 insertions(+), 19 deletions(-) diff --git a/bun.lock b/bun.lock index 75eac4b34..fc32d2404 100644 --- a/bun.lock +++ b/bun.lock @@ -22,6 +22,7 @@ "eslint-stylistic-airbnb": "^2.0.1", "globals": "^17.3.0", "lefthook": "^2.1.6", + "tsdown": "^0.22.1", "turbo": "^2.9.14", "typescript": "catalog:", "typescript-eslint": "^8.56.1", @@ -689,7 +690,7 @@ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.6", "", {}, "sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg=="], "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], @@ -955,7 +956,7 @@ "@mswjs/interceptors": ["@mswjs/interceptors@0.41.8", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-pRLMNKTSGRoLq+KnEB/7OY5vijw1XmcheAAOiv6pj7W1FG32kAGqj1C/RK/cqxRGr1Fh+zBi8sDur8kj3EQv6A=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="], @@ -1025,6 +1026,8 @@ "@ota-meshi/ast-token-store": ["@ota-meshi/ast-token-store@0.3.0", "", {}, "sha512-XRO0zi2NIUKq2lUk3T1ecFSld1fMWRKE6naRFGkgkdeosx7IslyUKNv5Dcb5PJTja9tHJoFu0v/7yEpAkrkrTg=="], + "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ=="], "@pagefind/darwin-x64": ["@pagefind/darwin-x64@1.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw=="], @@ -1053,8 +1056,40 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], + "@qwik.dev/partytown": ["@qwik.dev/partytown@0.13.2", "", { "dependencies": { "dotenv": "^16.4.7" }, "bin": { "partytown": "bin/partytown.cjs" } }, "sha512-Umls4bSkuzqLVcGvf8OgwIn/OldproSAbaQ/iYGe8VPYBpl2CaOSxabWwkeC72LDFqxVL0b0q8XlI8MuChDyzg=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], @@ -1287,6 +1322,8 @@ "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="], @@ -1493,7 +1530,7 @@ "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], + "ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], @@ -1513,6 +1550,8 @@ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-kit": ["ast-kit@3.0.0-beta.1", "", { "dependencies": { "@babel/parser": "^8.0.0-beta.4", "estree-walker": "^3.0.3", "pathe": "^2.0.3" } }, "sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw=="], + "ast-matcher": ["ast-matcher@1.2.0", "", {}, "sha512-utVZ8dtrpkeWMntA9u0MZV4Qh4uj+0ePxqVRaBAfLW0HAMf+kx2AX64W8B6pLK1Pcrt+sPoBPtH6j0thqsSkcQ=="], "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], @@ -1557,7 +1596,7 @@ "binaryextensions": ["binaryextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw=="], - "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], @@ -1595,7 +1634,7 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], @@ -1763,6 +1802,8 @@ "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="], + "dts-resolver": ["dts-resolver@3.0.0", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], @@ -1775,6 +1816,8 @@ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "empathic": ["empathic@2.0.1", "", {}, "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], @@ -2079,7 +2122,7 @@ "hono": ["hono@4.12.18", "", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="], - "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], @@ -2109,6 +2152,8 @@ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + "import-without-cache": ["import-without-cache@0.4.0", "", {}, "sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], @@ -2685,6 +2730,8 @@ "qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="], + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], @@ -2785,6 +2832,10 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.25.2", "", { "dependencies": { "@babel/generator": "8.0.0-rc.6", "@babel/helper-validator-identifier": "8.0.0-rc.6", "@babel/parser": "8.0.0-rc.6", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^3.0.0", "get-tsconfig": "5.0.0-beta.5", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20260325.1", "rolldown": "^1.0.0", "typescript": "^5.0.0 || ^6.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-nMhN/R+vmR8GM45ZW1FWMSjRTSDDn/6w4GTf8RNrEFCBdl8B1kySWrU1ixPtbwzXoRlcO+R/S88VgXuJQwfdDg=="], + "rollup": ["rollup@4.60.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.3", "@rollup/rollup-android-arm64": "4.60.3", "@rollup/rollup-darwin-arm64": "4.60.3", "@rollup/rollup-darwin-x64": "4.60.3", "@rollup/rollup-freebsd-arm64": "4.60.3", "@rollup/rollup-freebsd-x64": "4.60.3", "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", "@rollup/rollup-linux-arm-musleabihf": "4.60.3", "@rollup/rollup-linux-arm64-gnu": "4.60.3", "@rollup/rollup-linux-arm64-musl": "4.60.3", "@rollup/rollup-linux-loong64-gnu": "4.60.3", "@rollup/rollup-linux-loong64-musl": "4.60.3", "@rollup/rollup-linux-ppc64-gnu": "4.60.3", "@rollup/rollup-linux-ppc64-musl": "4.60.3", "@rollup/rollup-linux-riscv64-gnu": "4.60.3", "@rollup/rollup-linux-riscv64-musl": "4.60.3", "@rollup/rollup-linux-s390x-gnu": "4.60.3", "@rollup/rollup-linux-x64-gnu": "4.60.3", "@rollup/rollup-linux-x64-musl": "4.60.3", "@rollup/rollup-openbsd-x64": "4.60.3", "@rollup/rollup-openharmony-arm64": "4.60.3", "@rollup/rollup-win32-arm64-msvc": "4.60.3", "@rollup/rollup-win32-ia32-msvc": "4.60.3", "@rollup/rollup-win32-x64-gnu": "4.60.3", "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], @@ -2809,7 +2860,7 @@ "secretlint": ["secretlint@10.2.2", "", { "dependencies": { "@secretlint/config-creator": "^10.2.2", "@secretlint/formatter": "^10.2.2", "@secretlint/node": "^10.2.2", "@secretlint/profiler": "^10.2.2", "debug": "^4.4.1", "globby": "^14.1.0", "read-pkg": "^9.0.1" }, "bin": "./bin/secretlint.js" }, "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg=="], - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="], "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], @@ -2963,7 +3014,7 @@ "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="], - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], @@ -2997,6 +3048,8 @@ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + "tsdown": ["tsdown@0.22.1", "", { "dependencies": { "ansis": "^4.3.0", "cac": "^7.0.0", "defu": "^6.1.7", "empathic": "^2.0.1", "hookable": "^6.1.1", "import-without-cache": "^0.4.0", "obug": "^2.1.1", "picomatch": "^4.0.4", "rolldown": "^1.0.2", "rolldown-plugin-dts": "^0.25.1", "semver": "^7.8.0", "tinyexec": "^1.1.2", "tinyglobby": "^0.2.16", "tree-kill": "^1.2.2", "unconfig-core": "^7.5.0" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.22.1", "@tsdown/exe": "0.22.1", "@vitejs/devtools": "*", "publint": "^0.3.8", "tsx": "*", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0", "unrun": "*" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@vitejs/devtools", "publint", "tsx", "typescript", "unplugin-unused", "unrun"], "bin": { "tsdown": "./dist/run.mjs" } }, "sha512-Ldx1jLyDFEzsN/fMBi2TBVaZe4fuEJhIiHjQhX0pV7oa5uYz5Imdivs5mNzEXOrMEtFRR6C9BQ2YqLoroffB+Q=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], @@ -3035,6 +3088,8 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], "underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="], @@ -3203,12 +3258,12 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@antfu/install-pkg/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -3217,6 +3272,12 @@ "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@bomb.sh/tab/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -3233,6 +3294,10 @@ "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], + "@node-rs/crc32-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@peggyjs/from-mem/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], @@ -3247,6 +3312,8 @@ "@textlint/linter-formatter/pluralize": ["pluralize@2.0.0", "", {}, "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw=="], + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "@vscode/vsce/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -3255,12 +3322,18 @@ "@vscode/vsce/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "@vscode/vsce/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "agents/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], @@ -3269,9 +3342,11 @@ "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "ast-kit/@babel/parser": ["@babel/parser@8.0.0-rc.6", "", { "dependencies": { "@babel/types": "^8.0.0-rc.6" }, "bin": "./bin/babel-parser.js" }, "sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog=="], + "astro/get-tsconfig": ["get-tsconfig@5.0.0-beta.4", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ=="], - "astro/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "astro/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "astro-iconify/svgo": ["svgo@3.3.3", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng=="], @@ -3293,6 +3368,8 @@ "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "eslint-compat-utils/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "eslint-plugin-jsonc/@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="], "eslint-plugin-n/eslint-plugin-es-x": ["eslint-plugin-es-x@7.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", "@eslint-community/regexpp": "^4.11.0", "eslint-compat-utils": "^0.5.1" }, "peerDependencies": { "eslint": ">=8" } }, "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ=="], @@ -3301,6 +3378,8 @@ "eslint-plugin-n/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "eslint-plugin-n/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], @@ -3319,12 +3398,18 @@ "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "jsonc-eslint-parser/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "node-abi/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "normalize-package-data/hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], @@ -3335,6 +3420,8 @@ "ovsx/commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="], + "ovsx/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -3345,12 +3432,20 @@ "postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + "rolldown-plugin-dts/@babel/generator": ["@babel/generator@8.0.0-rc.6", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.6", "@babel/types": "^8.0.0-rc.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ=="], + + "rolldown-plugin-dts/@babel/parser": ["@babel/parser@8.0.0-rc.6", "", { "dependencies": { "@babel/types": "^8.0.0-rc.6" }, "bin": "./bin/babel-parser.js" }, "sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog=="], + + "rolldown-plugin-dts/get-tsconfig": ["get-tsconfig@5.0.0-beta.5", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ=="], + "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "secretlint/read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "sitemap/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -3365,13 +3460,19 @@ "table/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "tsup/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "tsup/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "vite-plugin-vue-inspector/@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.5.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@vue/babel-helper-vue-transform-on": "1.5.0", "@vue/babel-plugin-resolve-type": "1.5.0", "@vue/shared": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw=="], + "vite-dev-rpc/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], - "vitest/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "vite-plugin-inspect/ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], + + "vite-plugin-vue-inspector/@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.5.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@vue/babel-helper-vue-transform-on": "1.5.0", "@vue/babel-plugin-resolve-type": "1.5.0", "@vue/shared": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw=="], "vscode-tmgrammar-test/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], @@ -3417,6 +3518,8 @@ "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "ast-kit/@babel/parser/@babel/types": ["@babel/types@8.0.0-rc.6", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.6", "@babel/helper-validator-identifier": "^8.0.0-rc.6" } }, "sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw=="], + "astro-iconify/svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "astro-iconify/svgo/css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="], @@ -3439,6 +3542,10 @@ "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "rolldown-plugin-dts/@babel/generator/@babel/types": ["@babel/types@8.0.0-rc.6", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.6", "@babel/helper-validator-identifier": "^8.0.0-rc.6" } }, "sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw=="], + + "rolldown-plugin-dts/@babel/parser/@babel/types": ["@babel/types@8.0.0-rc.6", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.6", "@babel/helper-validator-identifier": "^8.0.0-rc.6" } }, "sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw=="], + "secretlint/read-pkg/normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], "secretlint/read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], @@ -3493,14 +3600,22 @@ "agents/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "ast-kit/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.6", "", {}, "sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g=="], + "astro-iconify/svgo/css-tree/mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="], "npm-run-all/cross-spawn/shebang-command/shebang-regex": ["shebang-regex@1.0.0", "", {}, "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="], "npm-run-all/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "rolldown-plugin-dts/@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.6", "", {}, "sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g=="], + + "rolldown-plugin-dts/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.6", "", {}, "sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g=="], + "secretlint/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + "secretlint/read-pkg/normalize-package-data/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "vscode-tmgrammar-test/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], diff --git a/package.json b/package.json index c247c7bae..5f73e9d28 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "eslint-stylistic-airbnb": "^2.0.1", "globals": "^17.3.0", "lefthook": "^2.1.6", + "tsdown": "^0.22.1", "turbo": "^2.9.14", "typescript": "catalog:", "typescript-eslint": "^8.56.1", diff --git a/packages/integrations/vite/src/index.ts b/packages/integrations/vite/src/index.ts index 2bc46118b..e4087c725 100644 --- a/packages/integrations/vite/src/index.ts +++ b/packages/integrations/vite/src/index.ts @@ -295,11 +295,16 @@ See https://varlock.dev/integrations/vite/ for more details. // we do not want to inject via config.define - instead we use @rollup/plugin-replace // esbuild strips the node: prefix from imports, so varlock's built dist - // uses bare 'crypto' which Vite SSR doesn't auto-externalize - config.ssr ||= {}; - config.ssr.external ||= []; - if (Array.isArray(config.ssr.external) && !config.ssr.external.includes('crypto')) { - config.ssr.external.push('crypto'); + // uses bare 'crypto' which Vite SSR doesn't auto-externalize. + // Skip when CF Vite plugin is present — it rejects ssr.external since + // Workers handle node builtins via nodejs_compat. + const hasCfPlugin = config.plugins?.flat().some((p: any) => p?.name?.includes('cloudflare')); + if (!hasCfPlugin) { + config.ssr ||= {}; + config.ssr.external ||= []; + if (Array.isArray(config.ssr.external) && !config.ssr.external.includes('crypto')) { + config.ssr.external.push('crypto'); + } } if (!configIsValid) { diff --git a/packages/varlock/package.json b/packages/varlock/package.json index 3c182d5dd..a75dae268 100644 --- a/packages/varlock/package.json +++ b/packages/varlock/package.json @@ -137,6 +137,7 @@ "@clack/prompts": "^1.0.0", "@env-spec/parser": "workspace:*", "@env-spec/utils": "workspace:*", + "@gunshi/plugin-completion": "^0.32.0", "@sindresorhus/is": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:", @@ -145,7 +146,6 @@ "ansis": "catalog:", "browser-or-node": "^3.0.0", "ci-info": "^4.3.1", - "@gunshi/plugin-completion": "^0.32.0", "exit-hook": "^5.1.0", "gunshi": "^0.32.0", "is-docker": "^4.0.0", From 14bbc4df8b7386228a273928549097c64197b010 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 1 Jun 2026 14:36:17 -0700 Subject: [PATCH 8/8] chore: address PR review comments - Use dynamic encryption keys in framework tests instead of hardcoded - Improve docs wording for encrypted env blob notes --- framework-tests/frameworks/cloudflare/cloudflare-shared.ts | 3 ++- framework-tests/frameworks/expo/expo.test.ts | 3 ++- framework-tests/frameworks/nextjs/nextjs-shared.ts | 3 ++- framework-tests/frameworks/vite/vite-shared.ts | 3 ++- .../varlock-website/src/content/docs/integrations/nextjs.mdx | 2 +- .../varlock-website/src/content/docs/integrations/vite.mdx | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/framework-tests/frameworks/cloudflare/cloudflare-shared.ts b/framework-tests/frameworks/cloudflare/cloudflare-shared.ts index 2b5ebf1fc..24e2d74db 100644 --- a/framework-tests/frameworks/cloudflare/cloudflare-shared.ts +++ b/framework-tests/frameworks/cloudflare/cloudflare-shared.ts @@ -2,6 +2,7 @@ Shared Cloudflare Workers test definitions, parameterized by Vite version. Covers basic worker dev, leak detection, build + preview, and large env chunking. */ +import { randomBytes } from 'node:crypto'; import { describe, beforeAll, afterAll, } from 'vitest'; @@ -167,7 +168,7 @@ export function defineCloudflareTests( command: `vite dev --port ${basePort + 5}`, readyPattern: /Local:.*http/, readyTimeout: 30_000, - env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + env: { _VARLOCK_ENV_KEY: randomBytes(32).toString('hex') }, templateFiles: { 'src/index.ts': 'workers/basic-worker.ts', 'vite.config.ts': 'vite-configs/vite.config.ts', diff --git a/framework-tests/frameworks/expo/expo.test.ts b/framework-tests/frameworks/expo/expo.test.ts index a87b452cc..73cbb0c64 100644 --- a/framework-tests/frameworks/expo/expo.test.ts +++ b/framework-tests/frameworks/expo/expo.test.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'node:crypto'; import { describe, beforeAll, afterAll, } from 'vitest'; @@ -149,7 +150,7 @@ describe('Expo Integration', () => { describe('encrypted env blob', () => { expoEnv.describeScenario('build succeeds with _VARLOCK_ENV_KEY', { command: 'node build.mjs', - env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + env: { _VARLOCK_ENV_KEY: randomBytes(32).toString('hex') }, templateFiles: { 'app/page.tsx': 'pages/basic-page.tsx', }, diff --git a/framework-tests/frameworks/nextjs/nextjs-shared.ts b/framework-tests/frameworks/nextjs/nextjs-shared.ts index 505b62b38..86c73922e 100644 --- a/framework-tests/frameworks/nextjs/nextjs-shared.ts +++ b/framework-tests/frameworks/nextjs/nextjs-shared.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'node:crypto'; import { describe, beforeAll, afterAll, } from 'vitest'; @@ -348,7 +349,7 @@ export function defineNextjsTests(nextVersion: number, testDir: string) { nextEnv.describeScenario('encrypted env blob with _VARLOCK_ENV_KEY', { command: buildCommand, - env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + env: { _VARLOCK_ENV_KEY: randomBytes(32).toString('hex') }, templateFiles: { 'app/page.tsx': 'pages/basic-page.tsx', }, diff --git a/framework-tests/frameworks/vite/vite-shared.ts b/framework-tests/frameworks/vite/vite-shared.ts index ec580ea34..859486013 100644 --- a/framework-tests/frameworks/vite/vite-shared.ts +++ b/framework-tests/frameworks/vite/vite-shared.ts @@ -3,6 +3,7 @@ Shared Vite test definitions, parameterized by Vite version. Covers static builds, HTML constant replacement, leak detection, log redaction, sourcemap scrubbing, SSR init injection, and dev server. */ +import { randomBytes } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { @@ -261,7 +262,7 @@ export function defineViteTests( describe('encrypted env blob', () => { viteEnv.describeScenario('SSR build with _VARLOCK_ENV_KEY encrypts the blob', { command: 'vite build --ssr src/ssr-entry.ts', - env: { _VARLOCK_ENV_KEY: '846a4cbdf4fefeff0da38d8f3766ffe50d8db12f8ce32849bb1e1a60ecb4ba0d' }, + env: { _VARLOCK_ENV_KEY: randomBytes(32).toString('hex') }, templateFiles: { 'vite.config.ts': 'vite-configs/vite.config.resolved-env.ts', 'index.html': 'html/basic.html', diff --git a/packages/varlock-website/src/content/docs/integrations/nextjs.mdx b/packages/varlock-website/src/content/docs/integrations/nextjs.mdx index 9605e81ba..4f913a507 100644 --- a/packages/varlock-website/src/content/docs/integrations/nextjs.mdx +++ b/packages/varlock-website/src/content/docs/integrations/nextjs.mdx @@ -328,7 +328,7 @@ varlock run -- node .next/standalone/server.js --- :::note[Encrypting the env blob] -When deploying to serverless platforms like Vercel, varlock injects the resolved env into your server-side build output as plaintext JSON. This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. +When deploying to serverless platforms like Vercel, Varlock injects the fully resolved env data into your server-side build output. By default, this blob is plaintext JSON — meaning anyone with access to the build artifact can read your secrets. You can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. ::: --- diff --git a/packages/varlock-website/src/content/docs/integrations/vite.mdx b/packages/varlock-website/src/content/docs/integrations/vite.mdx index 07318583f..26d9437ba 100644 --- a/packages/varlock-website/src/content/docs/integrations/vite.mdx +++ b/packages/varlock-website/src/content/docs/integrations/vite.mdx @@ -244,7 +244,7 @@ All non-sensitive items are bundled at build time via `ENV`, while `import.meta. --- :::note[Encrypting the env blob] -When deploying to serverless platforms, varlock injects the resolved env into your server-side build output as plaintext JSON (when using `ssrInjectMode: 'resolved-env'`). This is generally safe since it only appears in server code, but you can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. +When using `ssrInjectMode: 'resolved-env'`, Varlock injects the fully resolved env data into your SSR build output. By default, this blob is plaintext JSON — meaning anyone with access to the build artifact can read your secrets. You can encrypt it for extra protection (e.g., against sourcemap leaks). See the [encrypted deployments guide](/guides/encrypted-deployments/) for setup instructions. ::: ---