DSH plugin credentials: CredentialRef and per-call resolve

Configuration & UsagePublished 2026-10-17Author: DeepSeek Plugin Market
DeepSeek HarnessDSH pluginCredentialRefcredentialsredactionseam
How DSH plugin credentials work: config carries CredentialRef only, consumers re-resolve per call, and records use modifyRecord as the sole write path.

The DSH plugin credential seam has one unbreakable rule: configuration carries references, never values. Settings sections and cordis.yml entries hold CredentialRef (an environment-variable name) while the actual secret belongs to a provider such as dsh-credentials-local; consumers re-resolve on every operation, so a rotated credential reaches the next request without a restart. The design also enforces a hard rule: an empty stored value is absent everywhere — resolve skips it and describe reports it unconfigured, so a blank can never masquerade as a configured secret. With those two ideas, reads, writes and descriptions all fall into place.

DSH plugin credential baseline: references in config, values in providers

Bottom line: a reference may live in configuration; the value belongs to the provider — that is what makes credentials safe to back up and share.

  1. Configuration holds references: settings store environment-variable names, and cordis.yml entries do the same.
  2. The provider owns values: the local provider reads from the process environment, its managed store and .env files.
  3. Empty means absent: a blank stored value is never treated as configured.
  4. Audit before committing: make sure no key-shaped string appears in configuration.
  5. Verify: share the configuration with another machine — references remain, but the value must be configured separately.

DSH plugin's two key spaces: references and records

Bottom line: a reference answers "what is behind this environment-variable name"; a record answers "what does this plugin hold for this id."

Key spaceQuestionLayeredMain operations
CredentialRefWhat is behind the variableYes (env / store / .env)resolve, describe, set, unset
CredentialKeyWhat the plugin holds for an idNoreadRecord, describeRecord, listRecords, modifyRecord, deleteRecord
  1. References cover secrets: model API keys, third-party tokens.
  2. Records cover grants: plugin-owned authorization such as OAuth.
  3. References have no enumeration: configuration surfaces learn them from settings schemas; records must be enumerable so a UI can show grants and find orphans.
  4. Do not mix them: resolving a record returns nothing.

DSH plugin per-operation resolve: why not to cache

Bottom line: re-resolving each operation is how hot rotation works; caching shuts rotation out.

  1. Call shape:
ts
const resolved = await ctx.credentials.resolve(ref)
if (!resolved) { /* unconfigured: report clearly, never request with an empty string */ }
  1. LLM adapters are the model: resolve once per model request, so rotation applies to the next one.
  2. Never cache across operations: the official contract forbids it; per-operation reads are the mechanism.
  3. Unconfigured is undefined: branch on it instead of falling back to an empty string.
  4. Verify: change the environment value without restarting and send the next request.

DSH plugin describe and CredentialInfo: why some references are read-only

Bottom line: describe returns facts and never values; writable: false means a read-only source shadows the reference.

ts
interface CredentialInfo {
  configured: boolean
  source?: string
  writable: boolean
}
  1. UI badges: configured state, supplying layer, and whether writing is possible.
  2. The local read-only case: references supplied by the process environment report writable: false; a write would appear to succeed while resolution keeps returning the shadowing value, so the seam rejects it.
  3. Render read-only early instead of letting users click and fail.
  4. Change notification: credentials/reference-updated fires after set, unset or an observed external edit; process environment changes are unobservable and never emit.
  5. Consumers do not need the event: per-operation resolution is enough; the event serves the UI.

DSH plugin writes: set/unset and modifyRecord

Bottom line: references use set/unset; records use only modifyRecord, the one safe way to refresh a token.

  1. Reference writes:
ts
await ctx.credentials.set(ref, value)   // empty values are rejected; use unset to remove
await ctx.credentials.unset(ref)        // removing an absent reference is a no-op
  1. Shadowed writes are rejected: both set and unset refuse while a read-only source shadows the reference.
  2. Record writes:
ts
await ctx.credentials.modifyRecord(key, async (current) => next)  // undefined leaves it untouched
  1. Why not read-then-write: two processes refreshing the same token would lose the first write; modifyRecord holds one lock for the whole read-decide-replace.
  2. Authorization has its own entry: ctx.authorization registers credential-obtaining flows, one attempt per key, and a duplicate registration fails with DUPLICATE_FLOW.

DSH plugin credential troubleshooting

Bottom line: split failures into cannot read, cannot write and cannot see, then check shadowing or flow conflicts.

  1. Cannot read — resolve returns undefined; check the variable name and provider layer.
  2. Cannot write — check describe().writable (read-only shadowing).
  3. Cannot see it in UI — references are not enumerable; ensure the settings schema declares it.
  4. Rotation not applied — the plugin cached resolve results.
  5. Token refresh lost writes — switch to modifyRecord.
  6. Repair status, stated honestly: the two key spaces, empty-value rule, per-operation resolve and write refusal semantics follow the official User credentials page and package source notes; confirm signatures against the Cordis API block and your version.

Sources: User credentials, dsh-credentials source, Plugin config forms.

FAQ

Can a DSH plugin put an API key directly in config, or should it store a reference?

A DSH plugin config should carry a credential reference (an environment-variable name), never the secret itself. Settings sections and cordis.yml entries hold CredentialRef values while the provider owns the value, so configuration stays safe to share and back up.

Why must a DSH plugin avoid caching a resolved credential between operations?

DSH plugin credential resolution is per operation, and caching breaks hot updates: a rotated value would leave you calling with the old secret. Consumers re-resolve at each operation so rotation reaches the next call without a restart.

What does writable false mean in a DSH plugin credential description, and should I write anyway?

For DSH plugin credentials, writable false means a read-only source shadows the reference, for example the process environment. Against DSH plugin behavior, a write would appear to succeed while resolution keeps returning the shadowing value, so the seam rejects it and the UI renders it read-only.

How do CredentialRef and CredentialKey differ in DSH plugin credentials?

DSH plugin credentials have two key spaces: CredentialRef is an environment-variable name with layering, while CredentialKey is a stored record a plugin holds for an id. References cover secrets, records cover authorization grants.

Why must a DSH plugin refresh a token with modifyRecord instead of read-then-write?

DSH plugin modifyRecord is a serialized read-decide-replace under one lock, holding across processes where the store supports it. A DSH plugin doing read-then-write would lose the update when two processes refresh the same token concurrently.

Related Terms

CredentialRef
CredentialRef is a nominal reference to one credential, shaped as a POSIX-style environment-variable name and validated against shell identifier syntax at construction. It lets configuration carry references instead of values.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/credentials
CredentialInfo
CredentialInfo is the credential fact set for configuration UIs: whether it is configured, which layer supplies it, and whether it is writable, with no slot that could carry a value. That is what makes it safe to send across the Remote wire.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/credentials
CredentialKey
CredentialKey identifies a stored credential record a plugin holds for an id, such as an authorization grant. Unlike a reference it has no resolvable environment, so the record's presence is the whole fact.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/credentials
modifyRecord
modifyRecord is the only write path for credential records: a serialized read-modify-write over one record, holding a lock across processes where supported. It makes token refreshes safe from concurrent overwrites.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/credentials

Sources