Structure frontend repositories around a shared verify and delivery model. Use when standardizing package repos, app repos, or SDK repos across TypeScript, Swift, Kotlin, or similar ecosystems; setting up CI guardrails; defining a repo-local verify command; or enabling continuous publish or deploy flows on main after verify passes.
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Use this reference when a put.io frontend-owned repo has 1Password-backed local, live-test, build, signing, or deploy workflows. Defines the repo-side mechanics — detect, scaffold, verify — and is self-contained: external contributors and future agents can apply it without access to internal operator docs.
Out of scope: repos with native non-task-runner build systems (e.g. Xcode + Fastlane), repos that hold signing material consumed by tools like match, and repos whose .env/.env.example carry plain device or runtime credentials rather than 1Password references. Those follow their repo-local setup.
rg -n 'op (run|inject|read|item|whoami|signin)|OP_SERVICE_ACCOUNT_TOKEN|op://|load-secrets-action' \
AGENTS.md README.md CONTRIBUTING.md SECURITY.md docs .github Makefile package.json build.gradle.kts Package.swift .env.example scripts tooling apps Tests src 2>/dev/null
test -f .env.example && cat .env.exampleRepos with no real 1Password hits and no .env.example need none of the below — leave them alone.
If .env.example already exists with bare-key placeholders for non-1Password values (e.g. local device creds), preserve those entries when migrating to op:// references; the operator owns mixed-content templates and must approve replacement.
A secrets-using repo carries four artefacts. The secrets-setup target runs once per worktree to materialize .env.local from the operator's 1Password session — that is the only op invocation in the routine flow. Frameworks (Vite, Next.js) auto-read .env.local; shell-script flows that need secrets in-process wrap in op run --env-file=.env.example -- <cmd>.
.env.example (literal op:// references)PUTIO_API_KEY=op://<vault>/<item>/<field>
PUTIO_TEST_USER=op://<vault>/<item>/<field>Vault and item names go literally into the file. They are operational metadata, not secrets — the access control is the 1Password vault itself, not the names. The same template works for both op inject and op run --env-file.
secrets-setup / secrets-clean targetsBody is identical across runners. OP_ACCOUNT is pinned inside the target so the recipe is hermetic across personal op, devbox SA token, and CI. op whoami pre-flight fails fast if 1Password is locked; op inject -f overwrites without prompting; output is mode 0600. secrets-clean removes the materialised .env.local before git worktree remove.
Naming convention follows the runner: hyphen for Make / just / shell, colon for npm-style. Behaviour is identical.
# Makefile
.PHONY: secrets-setup secrets-clean
secrets-setup:
OP_ACCOUNT=<account>.1password.com op whoami >/dev/null
OP_ACCOUNT=<account>.1password.com op inject -f -i .env.example -o .env.local
secrets-clean:
rm -f .env.local .env.local.* .env.local.swp// package.json
{ "scripts": {
"secrets:setup": "OP_ACCOUNT=<account>.1password.com op whoami >/dev/null && OP_ACCOUNT=<account>.1password.com op inject -f -i .env.example -o .env.local",
"secrets:clean": "rm -f .env.local .env.local.* .env.local.swp"
} }# justfile
secrets-setup:
OP_ACCOUNT=<account>.1password.com op whoami >/dev/null
OP_ACCOUNT=<account>.1password.com op inject -f -i .env.example -o .env.local
secrets-clean:
rm -f .env.local .env.local.* .env.local.swpIn a monorepo with per-app/package .env.example files, declare the target on each package (e.g. apps/<app>/package.json's secrets:setup) so an agent can run pnpm --filter @org/<app> secrets:setup and materialise that one app's .env.local.
.gitignore.env
.env.*
!.env.exampleThe !.env.example exception is required — without it, the blanket .env.* rule silently un-tracks the template. Verify with git check-ignore -v .env.example (it must report no match).
Default verify (build, test, lint, typecheck) runs without secrets. Targets that need them declare secrets-setup as a task dependency:
live-test: secrets-setup
pnpm test:liveFor no-disk-persist flows (CI live-test, heightened-security one-shots), wrap in op run instead:
op run --env-file=.env.example -- pnpm test:liveKeep secrets-setup out of prepare, postinstall, and prebuild hooks — those run on pnpm install and would route every contributor through the 1Password bootstrap.
git check-ignore -v .env.local
git check-ignore -v .env.example && echo "WRONG: .env.example must be tracked" || true
op whoami --account=<account>.1password.com >/dev/null
<repo's secrets-setup target>
test -f .env.local
mode=$(stat -f '%Mp%Lp' .env.local 2>/dev/null || stat -c '%a' .env.local)
test "$mode" = "600" && echo mode ok || echo "WRONG: mode=$mode"
grep -q 'op://' .env.local && echo "WRONG: unresolved op:// in .env.local" || echo refs ok
<repo's secrets-clean target>
test ! -f .env.local && echo cleanup ok.env.local. If any depend on secrets, move the secret-dependent flow to a separate target (live-test, deploy, release) that documents its requirementsecrets-setup is a committer-only target in public repos; routine contributors ignore itop:// referenceMandatory shape against PR-driven exfiltration and supply-chain attacks.
pull_request MUST NOT map OP_SERVICE_ACCOUNT_TOKEN (or any sensitive secret) into any job. pull_request from internal branches DOES receive secrets.* when referenced; the gate is "workflow author never wires it in"push: main or workflow_dispatch reference Environment-scoped secrets gated by required reviewers. workflow_dispatch is only allowed when the Environment's deployment-branch policy restricts the runnable ref to main or protected release branchespull_request_target for code-running steps (checkout PR head, label automation with checkout, composite actions running PR-supplied scripts)workflow_run triggered by a pull_request workflow that reads PR data — classic exfiltration vectorpermissions: {} (deny by default); each job opts into the minimum it needs1Password/load-secrets-action@<sha> reading OP_ENV_FILE=.env.exampleLoad-bearing:
github-actions ecosystem so pinned SHAs get reviewable bumpsAdditional hygiene (adopt where team size supports a real PR review process):
main: required PR review, no force-push, no admin bypass.github/workflows/**, .github/actions/**, .env.example, the secrets-setup/secrets-clean target body, and lockfilesResidual risk for a yolopush-to-main team: a compromised committer credential = direct push = workflow runs in main context. The Environment human gate is the floor.
One-time per repo:
# Create the Deployment Environment (idempotent)
gh api -X PUT repos/<owner>/<repo>/environments/release
# Add the SA token as an Environment secret
gh secret set OP_SERVICE_ACCOUNT_TOKEN --env release --repo <owner>/<repo>
# Configure required reviewers, Prevent self-review, and deployment-branch policy
# in Settings → Environments → release (UI; gh api supports it but the body shape is awkward)Workflow YAML for a deploy / release / live-test job:
jobs:
deploy:
environment: release
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@<sha>
- uses: 1Password/load-secrets-action@<sha>
with:
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
OP_ENV_FILE: .env.example
- run: pnpm deployMigrating an existing repo Actions secret to the Environment: add the secret to the Environment first, switch the workflow's job to declare environment:, then delete the repo-level secret.
Cache keys include ${{ github.event_name }} so PR (no-secrets) jobs cannot poison caches consumed by push: main (with-secrets) jobs.
The same op calls and the same secrets-setup target work in all three contexts.
| Context | Credential source | Setup |
|---|---|---|
| Local laptop | Unlocked 1Password CLI session via desktop integration | Desktop integration on, biometric unlock, finite auto-lock, app unlocked at session start |
| Shared devbox | OP_SERVICE_ACCOUNT_TOKEN exported into the shared user's shell. Practical path: /etc/<org>/op.env (mode 0600, sourced from /etc/profile.d/<org>-op.sh or ~/.zshenv) | Operator pre-installs. Verify with ssh user@host 'env | grep OP_SERVICE_ACCOUNT_TOKEN' |
| Cloud agent (Codex Cloud, Claude Code Cloud) | OP_SERVICE_ACCOUNT_TOKEN configured as a workspace secret | One-time setup per workspace |
git worktree add ../<repo>.<topic> <branch>
cd ../<repo>.<topic>
<runner> secrets-setup # when the task chain needs .env.local (or `<runner> secrets:setup` for npm-style)
<runner> secrets-clean # before `git worktree remove` (or `<runner> secrets:clean`).env.local is materialised per-worktree; worktrees never share state.
The line is "anything you did not author personally", not "anything from a fork." Compromised internal accounts and malicious dependencies are real vectors. Mitigations per context: