Credential Source Pattern — Design
Status: Open — design proposal for the roadmap item Credential Source Pattern.
Problem
This is a unification task, not a greenfield credential feature. jackin❯ already has auth forwarding, env/provider-key handling, credential picker flows, structured 1Password references, and jackin-exec command mediation. Remaining work is one source-reference pattern that all of those mechanisms can share across env vars, provider tokens, 1Password/keychain references, MCP tools, and future daemon adapters.
jackin❯ already has several credential-resolution paths, documented in Environment Variables and Authentication. They share the shipped EnvValue vocabulary for operator env, auth forms, and on-demand delivery, but they do not yet share one general typed credential-source abstraction usable by non-env consumers.
Several upcoming items need more sources:
- GitHub link tracking needs a GitHub PAT.
- Task source abstraction may need per-source tokens.
- 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 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 (documents all three sources — keychain, env, command — plus
populate-git-credentials) - Config —
config.toml[github]block - Source —
lib/src/services/config.rs(TOML deserializer for the tagged-union token form)
[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.
Target TOML shape
# 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" }Target backends:
- 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. env— read host-operator env var; error if unset. This is never broad environment inheritance into the agent container.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.op— route to 1Password CLI using the current operator-env behavior.- OS secret stores — Apple Keychain via
securityon macOS, libsecret/secret-toolon Linux, and Windows Credential Manager when supported. New backend. 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 use a secret wrapper (
SecretStringor equivalent) with zeroize-on-drop, no Display impl, and redacted 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 an explicit per-consumer delivery setting. Launch-time env injection, read-only file mounts, and on-demand jackin-exec / host-bridge delivery must all consume the same source type, and each consumer decides where the resolved value is allowed to appear. Resolved secrets must never be written to the agent's persisted state directory.
Open design questions
- Literal-as-secret warning. Should jackin❯ refuse to load a literal where a
CredentialSourceis 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-toolare 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. 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.
Expected code touchpoints
- New module (e.g.
src/credential.rs) —CredentialSourceenum + resolver. crates/jackin/src/cli/config.rs— TOML deserialization.- Existing
EnvValue/OpRefresolution path (crates/jackin-core/src/env_value.rs) — adapted to or refactored behind the unified type. - New docs page:
docs/content/docs/(public)/guides/credentials.mdx.
Related work
- Roadmap: Credential Source Pattern
- Container Credential Exposure — Beyond Env Injection — adjacent design on how resolved credentials reach the container, not how they're sourced.
- 1Password integration — existing consumer.
- Claude auth strategy — existing consumer.
- GitHub link tracking, Task source abstraction — new consumers.
- jackin-remote — credential forwarding model.