Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5e09db0
mac/swift encryption binary + cross platform client
theoephraim Apr 8, 2026
a2a5563
gh actions changes
theoephraim Apr 8, 2026
af4e33f
add rust binary for windows/linux
theoephraim Apr 9, 2026
6338b15
add reveal command
theoephraim Apr 9, 2026
653c7b1
update docs
theoephraim Apr 9, 2026
b910a34
switch linux rust binary to musl for WSL2 compatibility
theoephraim Apr 9, 2026
a5c6b93
add WSL2 support for Windows Hello + binary size logging in CI
theoephraim Apr 9, 2026
14e7042
add test.name to vitest configs for labeled CI reports
theoephraim Apr 9, 2026
ec210f5
add WSL2 debug logging, file fallback warning, and bundle .exe in Lin…
theoephraim Apr 9, 2026
18d12d8
fix unreachable return after diverging match in cmd_decrypt
theoephraim Apr 9, 2026
b50854c
fix Windows build: import CommandExt for creation_flags
theoephraim Apr 9, 2026
dc10b2d
optimize WSL2 performance: cache binary path, skip redundant .exe spawns
theoephraim Apr 9, 2026
ab2c8ae
harden WSL2 security: pipe ACL + stdin data passing + TTY session sco…
theoephraim Apr 9, 2026
04484dd
add process verification and memory protection for key material
theoephraim Apr 9, 2026
6a2d419
fix varlock() prompt write-back and daemon spawn race conditions
theoephraim Apr 10, 2026
7a31401
harden native binaries: peer verification, IPC security, and build im…
theoephraim Apr 10, 2026
bf11e40
fix Windows build: correct windows crate 0.58 API types in pipe ACL s…
theoephraim Apr 10, 2026
d4757ea
fix Windows build: wrap NO_INHERITANCE in ACE_FLAGS type
theoephraim Apr 10, 2026
9e13e3f
fix WSL2 encrypt: pass data via stdin to avoid arg mangling
theoephraim Apr 10, 2026
39a1dd7
feat: add keychain() resolver for reading macOS Keychain items
theoephraim Apr 11, 2026
5cfb254
feat(encryption-binary-rust): use keyring + polkit biometrics on Linux
theoephraim Apr 13, 2026
a01cf0e
fix WSL2 daemon spawn: route through cmd.exe /c start, kill stale dae…
theoephraim Apr 13, 2026
f0327d8
fix WSL2 daemon spawn: drop DETACHED_PROCESS so `start /B` stays hidden
theoephraim Apr 13, 2026
7a6081b
fix WSL2 daemon: detect interop session, clear error + start-daemon h…
theoephraim Apr 13, 2026
0edf2ae
fix WSL2 daemon: spawn via Task Scheduler for interactive desktop ses…
theoephraim Apr 13, 2026
ae17eaa
feat(varlock encrypt): support reading value from stdin
theoephraim Apr 14, 2026
4e3649c
Fix WSL native binary resolution and daemon cold-start reliability
philmillman Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cuddly-oranges-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"varlock": patch
---

fix native binary resolution for bundled npm/WSL layouts by locating native-bins from the detected varlock package root, and improve WSL biometric decrypt reliability by prestarting the Windows daemon and polling readiness before first decrypt
5 changes: 5 additions & 0 deletions .changeset/red-wasps-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"varlock": minor
---

add new varlock() function for built-in encryption
65 changes: 65 additions & 0 deletions .github/workflows/binary-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ on:
release:
types: [published]

permissions:
contents: read

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
Expand All @@ -24,10 +27,42 @@ jobs:
run: |
echo "$GITHUB_CONTEXT"

# Build and sign the macOS native binary (cache hit if already built in CI)
build-native-macos:
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'varlock@')
uses: ./.github/workflows/build-native-macos.yaml
with:
mode: release
version: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
artifact-name: native-bin-macos-signed
secrets:
OP_CI_TOKEN: ${{ secrets.OP_CI_TOKEN }}

# Notarize the signed binary for production distribution
notarize-native-macos:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
needs: build-native-macos
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'varlock@')
uses: ./.github/workflows/notarize-native-macos.yaml
with:
source-artifact-name: native-bin-macos-signed
artifact-name: native-bin-macos-release
secrets:
OP_CI_TOKEN: ${{ secrets.OP_CI_TOKEN }}

# Build Rust native binaries for Linux and Windows
build-native-rust:
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'varlock@')
uses: ./.github/workflows/build-native-rust.yaml
with:
artifact-name: native-bin-rust

release-binaries:
needs: [notarize-native-macos, build-native-rust]
# was using github.ref.tag_name, but it seems that when publishing multiple tags at once, it was behaving weirdly
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'varlock@')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Setup Bun
Expand Down Expand Up @@ -63,6 +98,36 @@ jobs:
echo "RELEASE_TAG=varlock@${{ inputs.version }}" >> $GITHUB_ENV
echo "RELEASE_VERSION=${{ inputs.version }}" >> $GITHUB_ENV

# Download the signed macOS native binary
- name: Download macOS native binary
uses: actions/download-artifact@v8
with:
name: native-bin-macos-release
path: packages/varlock/native-bins/darwin/VarlockEnclave.app
- name: Restore native binary execute permission
run: chmod +x packages/varlock/native-bins/darwin/VarlockEnclave.app/Contents/MacOS/varlock-local-encrypt

# Download Rust native binaries for Linux and Windows
- name: Download Linux x64 native binary
uses: actions/download-artifact@v8
with:
name: native-bin-rust-linux-x64
path: packages/varlock/native-bins/linux-x64
- name: Download Linux arm64 native binary
uses: actions/download-artifact@v8
with:
name: native-bin-rust-linux-arm64
path: packages/varlock/native-bins/linux-arm64
- name: Download Windows x64 native binary
uses: actions/download-artifact@v8
with:
name: native-bin-rust-win32-x64
path: packages/varlock/native-bins/win32-x64
- name: Restore Rust binary execute permissions
run: |
chmod +x packages/varlock/native-bins/linux-x64/varlock-local-encrypt
chmod +x packages/varlock/native-bins/linux-arm64/varlock-local-encrypt

- name: build libs
run: bun run build:libs
env:
Expand Down
225 changes: 225 additions & 0 deletions .github/workflows/build-native-macos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
name: Build macOS native binary

# Reusable workflow that compiles, bundles, and Developer ID signs the
# VarlockEnclave Swift binary on a macOS runner.
#
# The Swift .build directory is cached by source hash, so the compile
# step (~minutes) is near-instant on cache hit. The .app bundle wrapping
# (plist, icon, signing) always runs since it varies by mode/version.
#
# Notarization is intentionally NOT included here — it's a separate
# workflow for production releases.

permissions:
contents: read

on:
workflow_call:
inputs:
mode:
description: 'Build mode: dev, preview, or release (affects bundle metadata)'
type: string
default: 'preview'
version:
description: 'Bundle version string (e.g. 1.2.3)'
type: string
default: '0.0.0-preview'
artifact-name:
description: 'Name for the uploaded artifact'
type: string
default: 'native-bin-macos'
secrets:
OP_CI_TOKEN:
required: true

jobs:
build-swift-binary:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6

- name: Setup Bun
uses: oven-sh/setup-bun@v2

# skip bun dep caching since less likely to hit

- name: Install node deps
run: bun install

- name: Enable turborepo build cache
uses: rharkor/caching-for-turbo@v2.3.11

# Cache the Swift .build directory so compilation is fast on unchanged source
- name: Compute Swift source hash
id: swift-hash
run: |
HASH=$(find packages/encryption-binary-swift/swift -type f | sort | xargs shasum -a 256 | shasum -a 256 | cut -d' ' -f1)
echo "hash=$HASH" >> $GITHUB_OUTPUT
echo "Swift source hash: $HASH"

- name: Cache Swift build artifacts
uses: actions/cache@v5
with:
path: packages/encryption-binary-swift/swift/.build
key: varlock-swift-build-${{ steps.swift-hash.outputs.hash }}

# Build varlock JS so we can use it to resolve secrets from 1Password
- name: Build varlock libs
run: bun run build:libs

# Load secrets from 1Password via varlock (scoped to the Swift package)
- name: Load signing secrets
uses: dmno-dev/varlock-action@v1.0.1
with:
working-directory: packages/encryption-binary-swift
env:
OP_CI_TOKEN: ${{ secrets.OP_CI_TOKEN }}

# Import signing certificate into a temporary keychain
- name: Import signing certificate
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 24)

echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > $RUNNER_TEMP/certificate.p12

security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

security import $RUNNER_TEMP/certificate.p12 \
-P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 \
-k "$KEYCHAIN_PATH"

security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db

echo "APPLE_SIGNING_IDENTITY=$APPLE_SIGNING_IDENTITY" >> $GITHUB_ENV

# Compile (cached), bundle with mode-specific metadata, and sign
- name: Build, bundle, and sign
run: |
bun run --filter @varlock/encryption-binary-swift build:universal \
-- --mode ${{ inputs.mode }} --version ${{ inputs.version }} --sign "$APPLE_SIGNING_IDENTITY"

- name: Verify binary
run: |
APP_PATH="packages/varlock/native-bins/darwin/VarlockEnclave.app"
echo "=== App bundle contents ==="
ls -la "$APP_PATH/Contents/MacOS/"
echo "=== Binary architectures ==="
lipo -info "$APP_PATH/Contents/MacOS/varlock-local-encrypt"
echo "=== Code signature ==="
codesign -dvv "$APP_PATH" 2>&1 || true
echo "=== Info.plist ==="
cat "$APP_PATH/Contents/Info.plist"
echo ""
echo "=== Size summary ==="
BIN_SIZE=$(stat -f%z "$APP_PATH/Contents/MacOS/varlock-local-encrypt")
BUNDLE_SIZE=$(du -sk "$APP_PATH" | cut -f1)
echo "Binary: $(( BIN_SIZE / 1024 )) KB ($BIN_SIZE bytes)"
echo "📦 darwin/VarlockEnclave.app: ${BUNDLE_SIZE} KB (full bundle)"

# Test the binary (using --no-auth since CI has no biometric)
# Keys are still Secure Enclave-backed, just without user presence requirement
- name: Test binary - status
run: |
BIN="packages/varlock/native-bins/darwin/VarlockEnclave.app/Contents/MacOS/varlock-local-encrypt"
echo "=== status ==="
$BIN status
$BIN status | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['ok'], 'status not ok'"

- name: Test binary - SE key lifecycle + encrypt/decrypt roundtrip
run: |
BIN="packages/varlock/native-bins/darwin/VarlockEnclave.app/Contents/MacOS/varlock-local-encrypt"

echo "=== generate-key (--no-auth for CI) ==="
$BIN generate-key --key-id ci-test --no-auth

echo "=== key-exists ==="
$BIN key-exists --key-id ci-test | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['exists']"

echo "=== encrypt ==="
PLAINTEXT=$(printf 'hello from macOS CI' | base64)
CIPHERTEXT=$($BIN encrypt --key-id ci-test --data "$PLAINTEXT" | python3 -c "import sys,json; print(json.load(sys.stdin)['ciphertext'])")
echo "Ciphertext: ${CIPHERTEXT:0:40}..."

echo "=== decrypt (one-shot, no auth needed) ==="
DECRYPTED=$($BIN decrypt --key-id ci-test --data "$CIPHERTEXT" | python3 -c "import sys,json; print(json.load(sys.stdin)['plaintext'])")
echo "Decrypted: $DECRYPTED"

if [ "$DECRYPTED" != "hello from macOS CI" ]; then
echo "::error::Roundtrip failed! Expected 'hello from macOS CI', got '$DECRYPTED'"
exit 1
fi

echo "=== delete-key ==="
$BIN delete-key --key-id ci-test

echo "All macOS binary tests passed"

- name: Test JS→Swift interop
run: |
BIN="packages/varlock/native-bins/darwin/VarlockEnclave.app/Contents/MacOS/varlock-local-encrypt"

# Generate SE key (no auth for CI)
$BIN generate-key --key-id interop-test --no-auth

# Get the public key from the SE binary
PUBLIC_KEY=$($BIN generate-key --key-id interop-tmp --no-auth > /dev/null 2>&1; echo "skip")
# Actually, get public key by generating and reading the output
GEN_OUTPUT=$($BIN key-exists --key-id interop-test)

# Use the SE binary's encrypt to get the public key indirectly:
# generate-key already printed it — let's re-generate to capture it
$BIN delete-key --key-id interop-test > /dev/null
PUBLIC_KEY=$($BIN generate-key --key-id interop-test --no-auth | python3 -c "import sys,json; print(json.load(sys.stdin)['publicKey'])")
echo "SE Public Key: ${PUBLIC_KEY:0:20}..."

# Encrypt with JS using the SE public key
CIPHERTEXT=$(bun -e "
const { encrypt } = await import('./packages/varlock/src/lib/local-encrypt/crypto.ts');
const result = await encrypt('$PUBLIC_KEY', 'javascript to secure enclave');
process.stdout.write(result);
")
echo "JS Ciphertext: ${CIPHERTEXT:0:40}..."

# Decrypt with Swift SE binary (proves JS wire format is SE-compatible)
DECRYPTED=$($BIN decrypt --key-id interop-test --data "$CIPHERTEXT" | python3 -c "import sys,json; print(json.load(sys.stdin)['plaintext'])")

if [ "$DECRYPTED" != "javascript to secure enclave" ]; then
echo "::error::JS→Swift interop failed! Got: $DECRYPTED"
exit 1
fi
echo "✓ JS→Swift SE: '$DECRYPTED'"

# Cleanup
$BIN delete-key --key-id interop-test
echo "All macOS interop tests passed"

- name: Upload native binary artifact
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifact-name }}
path: packages/varlock/native-bins/darwin/VarlockEnclave.app
retention-days: 7

# Cache the signed .app so other jobs (e.g. release-preview) can restore
# it on a Linux runner without needing a macOS build
- name: Cache signed .app bundle
uses: actions/cache/save@v5
with:
path: packages/varlock/native-bins/darwin/VarlockEnclave.app
key: native-bin-macos-signed-${{ hashFiles('packages/encryption-binary-swift/swift/**') }}

- name: Cleanup signing keychain
if: always()
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
if [ -f "$KEYCHAIN_PATH" ]; then
security delete-keychain "$KEYCHAIN_PATH" || true
fi
rm -f $RUNNER_TEMP/certificate.p12
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Loading
Loading