Skip to content

feat(1password): bridge op CLI for devcontainer / remote use#624

Open
theoephraim wants to merge 1 commit intomainfrom
feature/op-container-bridge
Open

feat(1password): bridge op CLI for devcontainer / remote use#624
theoephraim wants to merge 1 commit intomainfrom
feature/op-container-bridge

Conversation

@theoephraim
Copy link
Copy Markdown
Member

@theoephraim theoephraim commented Apr 16, 2026

Summary

Inside a devcontainer the op CLI can't reach the host's 1Password desktop app for biometric auth, forcing users to fall back to service account tokens. This PR adds a varlock-op-bridge binary shipped with @varlock/1password-plugin that runs on the host and proxies op invocations over TCP (or a Unix socket). The plugin detects VARLOCK_OP_BRIDGE_SOCKET and routes every op call through the bridge transparently — so op doesn't need to be installed in the container and host biometric auth keeps working.

  • New varlock-op-bridge CLI with serve + idempotent ensure (suitable for devcontainer initializeCommand)
  • Token-based auth: 32-byte random token rotated per ensure, 0600 on host, bind-mounted read-only into the container
  • TCP and Unix-socket transports; TCP is the recommended default (Docker Desktop on macOS has known issues bind-mounting Unix sockets)
  • Env-blocklist server-side so the container can't clobber the host's HOME/USER/PATH when running op
  • argv[0] renaming so 1Password's auth prompt shows varlock-op-bridge instead of node

Architecture

┌───────────────── container ──────────────────┐          ┌──────────── host ────────────┐
│  varlock load                                │          │  varlock-op-bridge serve     │
│    └─ @varlock/1password-plugin              │  TCP     │    └─ spawn op               │
│         └─ invokeOpViaBridge(argv, env) ─────┼──────────┼─▶ run, stream result back    │
│             attaches token from env/file     │          │                              │
└──────────────────────────────────────────────┘          │  1Password desktop app       │
                                                          └──────────────────────────────┘

Single chokepoint in the plugin (cli-helper.ts): if VARLOCK_OP_BRIDGE_SOCKET is set, route through the bridge; else spawn op locally. No behavior change for existing users.

Setup (user-facing)

To be turned into proper docs later — keeping it here so the PR is self-contained.

Prereqs on the host

  • 1Password desktop app installed and unlocked
  • CLI integration enabled (Settings → Developer → Integrate with 1Password CLI)
  • Docker Desktop running

No host install of anything else is required — varlock-op-bridge ships with @varlock/1password-plugin and can be invoked via npx/pnpm dlx/bunx.

Add to your devcontainer.json

{
  // Runs on the HOST before the container starts; spins up the bridge if not
  // already running and rotates the shared-secret token. Idempotent.
  "initializeCommand": "npx -y -p @varlock/1password-plugin varlock-op-bridge ensure --addr 0.0.0.0:7195",

  // Inside the container, point the plugin at the host bridge and the token.
  "containerEnv": {
    "VARLOCK_OP_BRIDGE_SOCKET": "host.docker.internal:7195",
    "VARLOCK_OP_BRIDGE_TOKEN_FILE": "/run/varlock-op-bridge.token"
  },

  // Bind-mount the token file (0600 on host) read-only into the container.
  "mounts": [{
    "source": "${localEnv:HOME}/.varlock-op-bridge.token",
    "target": "/run/varlock-op-bridge.token",
    "type": "bind",
    "readonly": true
  }],

  // Linux hosts need this so `host.docker.internal` resolves. Harmless on macOS/Windows.
  "runArgs": ["--add-host=host.docker.internal:host-gateway"]
}

The other package-manager variants of the initializeCommand:

// pnpm
"initializeCommand": "pnpm dlx -p @varlock/1password-plugin varlock-op-bridge ensure --addr 0.0.0.0:7195"
// bun
"initializeCommand": "bunx --package @varlock/1password-plugin varlock-op-bridge ensure --addr 0.0.0.0:7195"

Why TCP on 0.0.0.0:7195?

  • Docker Desktop on macOS has chronic issues bind-mounting Unix sockets, so TCP over host.docker.internal is the portable default.
  • Must bind to 0.0.0.0 (not 127.0.0.1) because container connections arrive from Docker's VM gateway IP (e.g. 192.168.65.254), not loopback.
  • macOS's default firewall blocks inbound from the physical network, so 0.0.0.0 doesn't widely expose the port. Still, the token auth is the real gate.
  • If you prefer Unix sockets (Linux hosts, GitHub Codespaces, etc.), pass --addr /tmp/varlock-op-bridge.sock and use the same path for VARLOCK_OP_BRIDGE_SOCKET + the mounts source.

Confirming it's working

Inside the container:

# Should print resolved secrets, biometric prompt happens on host once per session
pnpm exec varlock load --format json

First call per 1Password session pops a biometric prompt on the host — it says "varlock-op-bridge wants to access 1Password" (not "node"). Approving once covers all subsequent calls until 1Password's session expires.

Troubleshooting

  • connect ECONNREFUSED 192.168.65.254:7195 — bridge is bound to 127.0.0.1 instead of 0.0.0.0. Use --addr 0.0.0.0:7195 in initializeCommand.
  • invalid mount config for type "bind": bind source path does not exist: /socket_mnt/... — you're on macOS using Unix-socket mode; switch to TCP.
  • cannot create directory "/home/<user>/.config/op" — container is leaking its HOME to the host. Fixed server-side via env-blocklist; pull latest.
  • **op bridge error: ENOENT: \op` not found on host** — install the op` CLI on the host.
  • unauthorized: invalid or missing bridge token — the mounts entry for the token file is missing, or the VARLOCK_OP_BRIDGE_TOKEN_FILE env var path is wrong.
  • Bridge not coming up — check ~/.varlock-op-bridge.log on the host.

Security notes

  • Host-local processes can read ~/.varlock-op-bridge.token if they have the user's UID (0600 file perms). Same threat model as SSH keys.
  • Sibling containers on the same host cannot auth without the token-file mount.
  • Network attackers are blocked by macOS's default firewall on the bridge port.
  • The bridge is a single shared op session for any caller with the token — it doesn't preserve 1Password's per-caller prompts. See discussion in src/bridge/server.ts.

Test plan

  • Unit-level smoke test: server + client round-trip with a fake op on PATH (success, non-zero exit → ExecError, env-overlay, ENOENT)
  • Token auth: rejects missing/wrong tokens, accepts correct token
  • ensure idempotent: fresh start, reuse, stale socket cleanup, token rotation restarts old bridge
  • TCP mode bound to 0.0.0.0 reachable via host.docker.internal inside devcontainer (verified end-to-end in a throwaway repo)
  • 1Password prompt shows "varlock-op-bridge" after argv[0] trick
  • Local bun run typecheck, bun run build, and bun run lint:fix all clean

🤖 Generated with Claude Code

Inside a devcontainer the `op` CLI can't reach the host's 1Password desktop
app for biometric auth, forcing users to fall back to service account tokens.

This adds a `varlock-op-bridge` binary shipped with @varlock/1password-plugin
that runs on the host and proxies `op` invocations over TCP or a Unix socket.
The plugin detects VARLOCK_OP_BRIDGE_SOCKET and routes every `op` call through
the bridge transparently, so `op` doesn't even need to be installed inside the
container and host biometric auth keeps working.

- `varlock-op-bridge serve` + idempotent `ensure` subcommands (suitable for
  devcontainer `initializeCommand`)
- Token-based auth: 32-byte random token rotated per `ensure`, 0600 on host,
  bind-mounted read-only into the container
- TCP and Unix-socket transports; TCP recommended for Docker Desktop on macOS
  where Unix socket bind-mounts are unreliable
- Client-side env blocklist so container-side HOME/USER/PATH can't clobber
  the host's values when running op
- argv[0] renaming so 1Password's auth prompt shows "varlock-op-bridge"
  rather than "node"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 16, 2026

🦋 Changeset detected

Latest commit: e28b181

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@varlock/1password-plugin

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 16, 2026

Open in StackBlitz

npm i https://pkg.pr.new/varlock@624
npm i https://pkg.pr.new/@varlock/1password-plugin@624
npm i https://pkg.pr.new/@varlock/akeyless-plugin@624
npm i https://pkg.pr.new/@varlock/doppler-plugin@624

commit: e28b181

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant