---
title: "Credential Source Pattern (cross-cutting)"
---
import RepoFile from '../../../../components/RepoFile.astro'

**Status**: Open — design pattern (Phase 5, [Agent Orchestrator Research Program](/reference/roadmap/agent-orchestrator-research/))

## Problem

jackin' already has several credential-resolution paths, documented in
[Environment Variables](/guides/environment-variables/) and
[Authentication](/guides/authentication/). They do not yet share one
typed credential-source abstraction.

Several upcoming items need *more* sources:

- [GitHub link tracking](/reference/roadmap/github-link-tracking/) needs
  a GitHub PAT.
- [Task source abstraction](/reference/roadmap/task-source-abstraction/)
  may need per-source tokens.
- [Claude auth strategy](/reference/roadmap/claude-auth-strategy/)
  needs a more durable Claude OAuth flow.
- A future Linear/JIRA/etc. source needs whatever those services use.

If each item adds its own source resolution code, drift is guaranteed.
The right move is a single cross-cutting pattern that any feature can
plug into.

This isn't a feature that ships on its own — it's a refactor that
unblocks several other features. The leaf exists so the design call is
made *once*, in one place, instead of being rediscovered five times.

## Why It Matters

- Without a unified pattern, adding a new credential source (Apple
  Keychain, Linux secret-tool, AWS Secrets Manager, Vault) means
  touching every consumer.
- multicode has already tried three sources (`env`, `command`, keychain)
  for one credential (their GitHub PAT) and converged on a small
  consistent shape. Borrowing the shape is much cheaper than designing
  one from scratch.
- It pairs with [jackin-remote](/reference/roadmap/jackin-remote/) and
  the future Kubernetes platform — both of which need to know how to
  resolve credentials *locally* and forward them *remotely*.

## Inspiration in multicode

**Sources**:
- README — [Authentication](https://github.com/graemerocher/multicode#authentication) (documents all three sources — keychain, env, command — plus `populate-git-credentials`)
- Config — [`config.toml` `[github]` block](https://github.com/graemerocher/multicode/blob/main/config.toml)
- Source — [`lib/src/services/config.rs`](https://github.com/graemerocher/multicode/blob/main/lib/src/services/config.rs) (TOML deserializer for the tagged-union token form)

```toml
[github]
# One of three forms:
token = { env = "GITHUB_TOKEN" }
token = { command = "gh auth token" }
token = { keychain-service = "multicode.github", keychain-account = "github-mcp-token" }
```

multicode also exposes `populate-git-credentials = true` for
auto-injecting the token as a git credential helper inside the
container.

The shape: a tagged-union TOML value with multiple resolver backends,
each backend implementing a small trait (`fn resolve() -> Result<SecretString>`).
Resolution happens at jackin-process startup (or per-invocation,
depending on the consumer).

## Recommended Shape

A `CredentialSource` type that's accepted anywhere a token/secret is
needed in TOML. The resolver vocabulary is small and explicit; no
implicit chains.

### TOML shape

```toml
# Direct literal — convenience, NOT recommended for real secrets
some_token = "sk-actual-value"

# Tagged forms
some_token = { env = "MY_VAR" }
some_token = { command = "gh auth token" }
some_token = { op = "op://Vault/Item/Field" }
some_token = { os_store = "jackin.token", account = "default" }
some_token = { file = "~/.secrets/token" }
```

Six backends:

1. **Literal** (string) — for non-secret values; emits a warning if a
   string field tagged `#[credential]` is supplied as a literal in
   any operator-config file outside `~/.config/jackin/local.toml`.
2. **`env`** — read host-operator env var; error if unset. This is never
   broad environment inheritance into the agent container.
3. **`command`** — run a command without a shell by default, with a minimal
   environment; the command must exit 0 and may only return the secret on
   stdout.
4. **`op`** — route to 1Password CLI using the current operator-env behavior.
5. **OS secret stores** — Apple Keychain via `security` on macOS,
   libsecret/`secret-tool` on Linux, and Windows Credential Manager when
   supported. New backend.
6. **`file`** — read file contents. Useful for K8s secret mounts and
   local development.

### Resolution behavior

- Resolution happens at the consumer's first need (lazy), not at
  config load. This means a misconfigured backend errors only when the
  feature using it actually runs.
- Resolved values implement `SecretString` (zeroize-on-drop, no
  Display impl, redacted in debug logs).
- Failures are explicit and contextual: `GitHub PAT (configured via [github].token = { command = ... }) failed: <stderr from command>`.
- Resolved values are not cached across runs of `jackin`. Within one
  run, they may be cached per-source-instance.

### Consumers

Each consumer (GitHub link tracker, task source, Claude auth, future
ones) accepts `Option<CredentialSource>` in its config and resolves
when it needs the value. No consumer reaches around the abstraction.

### Forwarding to containers

`populate_git_credentials` (multicode's name) generalizes to a
per-credential `forward_to_container = true` flag on the consumer's
config block. Implementation: jackin' writes the resolved secret into
a container env var or file mount per the consumer's spec, never to
the agent's persisted state directory.

## Scope (V1)

- `CredentialSource` enum + TOML deserialization.
- Six backends: literal, env, command, op, OS secret store, file.
- `SecretString` newtype with zeroize-on-drop.
- Replace existing ad-hoc credential lookups with the unified type while
  preserving the behavior documented under Environment Variables and
  Authentication.
- New consumers (GitHub link tracking, task sources) accept
  `CredentialSource` from day one.
- Documentation page: "Credential sources" — explains all six
  backends, security tradeoffs, recommended pattern per use case.

## Defer

- Vault / AWS Secrets Manager / GCP Secret Manager backends. Defer
  until a real user asks; the file-mount pattern + K8s secrets covers
  most of the space.
- Per-source resolution caching with TTL. Lazy-once-per-run is enough
  for V1.
- A `jackin secrets test` CLI that resolves every configured
  credential and reports status. Useful but small; defer.

## Open Questions

- **Literal-as-secret warning.** Should jackin' refuse to load a
  literal where a `CredentialSource` is expected (with override flag),
  or just warn loudly? Recommended: *warn loudly*; the convenience for
  non-secret tokens (e.g. a Slack webhook URL) outweighs strictness.
- **OS secret-store implementation.** macOS Keychain and Linux
  `secret-tool` are the first practical targets; Windows Credential Manager
  should share the same resolver shape once a Windows host story matters.
- **Forwarding interaction with [Claude auth
  strategy](/reference/roadmap/claude-auth-strategy/).** That item is
  already considering token plumbing; this pattern should *be* the
  abstraction it uses, not a parallel design. Recommended: *make this
  leaf land first* so the Claude auth work has the type to use.

## Related Files

- New module (e.g. `src/credential.rs`) —
  `CredentialSource` enum + resolver
- <RepoFile path="src/config/mod.rs" /> — TOML deserialization
- Existing `op://` resolution path — refactored to use the unified
  type
- Future consumers — link to this leaf as their credential plumbing
- New docs page: `docs/src/content/docs/guides/credentials.mdx`

## See Also

- [Agent Orchestrator Research Program](/reference/roadmap/agent-orchestrator-research/)
- [1Password integration](/reference/roadmap/onepassword-integration/) —
  existing consumer; refactored to use the new type
- [Claude auth strategy](/reference/roadmap/claude-auth-strategy/) —
  existing consumer; refactored to use the new type
- [GitHub link tracking](/reference/roadmap/github-link-tracking/) —
  new consumer
- [Task source abstraction](/reference/roadmap/task-source-abstraction/) —
  new consumer
- [jackin-remote](/reference/roadmap/jackin-remote/) — credential
  forwarding model
