Skip to content

Instantly share code, notes, and snippets.

@asaf400
Last active September 15, 2026 13:20
Show Gist options
  • Select an option

  • Save asaf400/2aba64b822e8cbcc0a1f38541ee7ff10 to your computer and use it in GitHub Desktop.

Select an option

Save asaf400/2aba64b822e8cbcc0a1f38541ee7ff10 to your computer and use it in GitHub Desktop.
1Password CLI wrapper for WSL and 1password integration for runtime secrets injection. Made by LLM
#!/usr/bin/env python3
"""Drop-in replacement for `op run` on WSL, where `op run` cannot exec Linux binaries.
`op.exe run -- cmd` spawns cmd as a *Windows* process, so it can never launch a
Linux program. This resolves every secret with a single `op.exe inject` call over
the interop pipe, then execs the target Linux process with the resolved
environment in place.
Usage:
op-run [--env-file FILE]... [VAR=op://vault/item/field]... -- cmd [args...]
op-run [--env-file FILE]... --print # emit shell-quoted exports
Env files accept `VAR=op://...` and `VAR={{ op://... }}`; `#` comments and blank
lines are ignored. Values that are not secret references are passed through
verbatim.
"""
from __future__ import annotations
import os
import re
import secrets
import shlex
import shutil
import subprocess
import sys
# Random per-run sentinel so a secret's own bytes can never be mistaken for a
# field delimiter. Secrets may legitimately contain newlines, quotes and '='
# (PEM keys, JSON blobs), which is why line-oriented parsing is not enough.
SENTINEL = f"__OPRUN_{secrets.token_hex(16)}__"
REF_RE = re.compile(r"^\{\{\s*(op://.*?)\s*\}\}$|^(op://.*)$", re.DOTALL)
NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
def die(msg: str) -> "NoReturn": # type: ignore[valid-type]
print(f"op-run: {msg}", file=sys.stderr)
raise SystemExit(2)
def find_op() -> str:
override = os.environ.get("OP_EXE")
if override:
return override
for candidate in ("op.exe", "/mnt/c/ProgramData/chocolatey/bin/op.exe"):
found = shutil.which(candidate) or (candidate if os.path.exists(candidate) else None)
if found:
return found
die("could not find op.exe on PATH; set OP_EXE to its full path")
def parse_env_file(path: str) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
with open(path, encoding="utf-8") as handle:
for lineno, raw in enumerate(handle, 1):
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("export "):
line = line[len("export ") :].lstrip()
if "=" not in line:
die(f"{path}:{lineno}: expected VAR=value, got {raw.rstrip()!r}")
name, value = line.split("=", 1)
pairs.append((name.strip(), _unquote(value.strip())))
return pairs
def _unquote(value: str) -> str:
if len(value) >= 2 and value[0] == value[-1] and value[0] in "'\"":
return value[1:-1]
return value
def resolve(pairs: list[tuple[str, str]], op_exe: str) -> dict[str, str]:
"""Resolve every op:// reference in one op.exe inject call.
Later assignments win, so `--env-file` secrets can be overridden by a
subsequent plain `VAR=value` (or the other way around).
"""
last: dict[str, str] = {}
for name, value in pairs:
if not NAME_RE.match(name):
die(f"invalid variable name {name!r}")
last[name] = value
resolved: dict[str, str] = {}
template_lines: list[str] = []
deferred: list[str] = []
for name, value in last.items():
match = REF_RE.match(value)
if match:
ref = match.group(1) or match.group(2)
template_lines.append(f"{name}={SENTINEL}{{{{ {ref} }}}}{SENTINEL}")
deferred.append(name)
else:
resolved[name] = value
if not deferred:
return resolved
template = "\n".join(template_lines) + "\n"
proc = subprocess.run(
[op_exe, "inject"],
input=template.encode(),
stdout=subprocess.PIPE,
stderr=None,
check=False,
)
if proc.returncode != 0:
raise SystemExit(proc.returncode)
out = proc.stdout.decode("utf-8", errors="surrogateescape")
found = dict(
re.findall(
rf"^([A-Za-z_][A-Za-z0-9_]*)={SENTINEL}(.*?){SENTINEL}$",
out,
re.DOTALL | re.MULTILINE,
)
)
missing = [name for name in deferred if name not in found]
if missing:
die(f"op.exe inject did not return values for: {', '.join(missing)}")
resolved.update(found)
return resolved
def main(argv: list[str]) -> int:
pairs: list[tuple[str, str]] = []
print_mode = False
command: list[str] = []
index = 0
while index < len(argv):
arg = argv[index]
if arg == "--":
command = argv[index + 1 :]
break
if arg == "--print":
print_mode = True
elif arg == "--env-file":
index += 1
if index >= len(argv):
die("--env-file requires a path")
pairs += parse_env_file(argv[index])
elif arg.startswith("--env-file="):
pairs += parse_env_file(arg.split("=", 1)[1])
elif "=" in arg and not arg.startswith("-"):
name, value = arg.split("=", 1)
pairs.append((name, value))
else:
die(f"unrecognised argument {arg!r}")
index += 1
if not pairs:
die("no secrets requested")
if not print_mode and not command:
die("no command given; use `-- cmd args` or --print")
resolved = resolve(pairs, find_op())
if print_mode:
for name, value in resolved.items():
print(f"export {name}={shlex.quote(value)}")
return 0
env = {**os.environ, **resolved}
try:
os.execvpe(command[0], command, env)
except FileNotFoundError:
die(f"command not found: {command[0]}")
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment