---
title: "One Token for Everything Is Still One Token"
description: "The case for scoping a service account to one purpose instead of 'everything' is easy to agree with in the abstract. Putting it into practice on a box that runs agents — not a CI runner that exits when the job ends — is where the argument gets tested. Here's what that looked like, including the part where the boundary pushed back."
canonical_url: "https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/"
md_url: "https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token.md"
published_at: "2026-06-07T00:00:00.000Z"
tags:
  - "ai-native"
  - "infrastructure"
  - "patterns"
  - "build-log"
---

## TL;DR

- The third post in this series made the case for scoping a service account token to a workflow's actual purpose instead of "the project" or "everything I have." Easy to agree with. The test is putting it into practice on a box that runs agents continuously — not a CI runner that exits when the job ends.
- In practice, that means a narrower vault for each purpose, with its own token, so a leak is bounded to exactly what that purpose touches — not "everything this box happens to do."
- In practice: moved a flat secrets file holding five mixed-purpose values — a search API key, an email API key, two internal dispatch secrets, plus config that isn't secret at all — into a vault scoped to exactly those five, behind a read-only token that can see nothing else.
- First attempt to populate that vault from the scoped token failed with a permission error. That's not friction to route around. That's separation of duties working — the box that runs agents consumes secrets, a separate session with a human behind it authors them.
- `op run` wraps the tool's launch command and resolves vault references into its environment at start time — the tool reads `process.env.API_KEY` exactly as before and never imports `op`.

---

[The third post in this series](https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/1password-infrastructure-not-password-manager) made the case for scoping a service account token to the workflow's actual purpose rather than to "the project" or "everything I've got" — a `ci-deploy` token that can see two items, not a master grant that happens to be wearing a service-account costume. The argument is right. Here's what it looks like to actually live inside it — on a box that runs agents, not a CI runner that exits when the job finishes.

The difference matters because the failure mode is different. A CI token's exposure window is the length of a job. An agent box's service account sits there, live, for as long as the box is up — reachable by anything the agent can be talked into doing, on every run, indefinitely. The scoping argument isn't theoretical there. It's the only thing standing between "an agent got confused once" and "an agent got confused once, near a token that could read everything."

## What scoping actually requires you to give up

The instinct, even after you've accepted the scoping argument in principle, is to scope generously "to be safe" — one vault per *box* rather than one per *purpose*, on the theory that fewer vaults means less to manage. That's the same mistake at one remove: a box that runs five different kinds of agent work ends up with one token that can read all five kinds of secret, and the blast radius is back to "everything this box touches," just relabeled.

The harder, correct version: scope to the purpose, even when the purpose is small and the setup feels like overkill for it.

## Scope the vault, then scope the token

Putting the principle into practice is structural, not procedural. Don't build a token with broad reach and trust it to behave — build a vault with narrow contents, and a token that can only ever see that vault. The token stops being something you have to trust, and becomes something that's simply incapable of the wider thing.

The concrete version of this: a flat secrets file on a remote box held five mixed-purpose values — a search API key, an email API key, two internal dispatch secrets — sitting alongside config values that weren't secrets at all. Anything that could read that file could read all five, regardless of which one a given task actually needed. The file was the vault, and it had no concept of scope.

The fix: a new 1Password vault holding exactly those five items and nothing else. A service account scoped to read-only access on that one vault — not "all vaults," not "everything I have." The flat file reduced to a single line: the token that opens that one narrow door, with a comment explaining what it can and can't do and where the actual secrets live now.

Compromise that token, and an attacker gets five bounded values scoped to one box's workflows — not the keys to everything the vault's owner has ever stored.

## Testing the boundary — on purpose

The first attempt to populate the new vault came from the scoped token itself, and it failed:

```
[ERROR] You do not have permission to perform this action
```

That's the system working exactly as configured. Read-only means read-only — including for the person who set it up, if they're using the scoped credential to do it. Population had to happen from a separate session, on a separate machine, with a human signed into the full account behind it.

That friction is the point, not a bug to engineer around. It draws a hard line between two roles that are easy to blur when one person is doing both: the place that *runs* agents, which only ever needs to *consume* credentials, and the place that *authors* credentials, which needs write access and a human in the loop. Collapse those into the same session and you've quietly rebuilt the single point of failure you just finished scoping away. Keep them separate, and a compromised agent runtime literally cannot rewrite its own permissions — there's no code path for it.

## Making the tool vault-transparent

None of this required touching the tool that actually uses the search API key. It already checked an environment variable first and fell back to a local config file second — written months before any of this existed. [The sixth post in this series](https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/1password-op-run-tool-transparent) covers exactly this shape: wrap the launch command in `op run --env-file=...`, and the vault reference resolves into the process environment at the moment the tool starts. The tool reads `process.env.API_KEY` exactly like it always did. The only thing that changed is where that value originates — and the tool has no way to know, or care.

Wrap what you didn't write. Don't fork it to teach it about the vault.

## What's still true after all of this

Scoping the vault and the token shrinks the blast radius from "everything" to "exactly what this one workflow needs." It's a real reduction — five bounded values instead of an open door to the whole account.

It does not make the secret disappear. It still lands in the tool's process environment for the life of that process, the same as it always did — just for a narrower set of values, reachable by a narrower set of things. Smaller blast radius. Not zero. That's the next question worth sitting with, and one worth coming back to.

## So what

Agreeing that a token should be scoped to its purpose costs nothing. Building the vault that makes the wider grant *impossible* — rather than merely discouraged — costs an afternoon, a permission error you have to sit with instead of route around, and the discipline to keep "the box's vault" from quietly becoming "the project's vault" the next time something new needs a credential.

That's what the principle looks like once it has to survive contact with a box that's up at 3am running something you didn't watch it start. Five bounded values, one read-only token, a permission error that fired exactly when it should have. Boring. Which is the point — boring is what it looks like when the blast radius was decided in advance instead of discovered during incident response.

---

*This post extends [the third](https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/1password-infrastructure-not-password-manager) and [sixth](https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/1password-op-run-tool-transparent) posts in the eight-post series on 1Password as infrastructure — applying the service-account and `op run` patterns with a narrower scope than "one token for everything." [The eighth post](https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/your-agent-doesnt-need-the-secret-just-the-result) goes further: a scoped, read-only token is still a value that lands in the tool's process. Brokering removes it from that process entirely. [Start from the first post](https://artificialcuriositylabs.ai/posts/1password-scope-the-vault-not-just-the-token/1password-builder-stack).*
