dsh-credentials-mysql
DeepSeek Harness 基于 MySQL 的后端凭证库插件 (ctx.credentials)
- Stars
- 0
- Language
- TypeScript
- Created
- Sep 2, 2026
- Updated
- Sep 4, 2026
Introduction
@sandersyao/dsh-credentials-mysql
English | 中文
The MySQL credentials vault for the DeepSeek Harness — a concrete CredentialProvider (the dsh-credentials seam). Load it as a plugin; it registers ctx.credentials and persists both key spaces (refs and records) into MySQL, behavior-contract-equivalent to dsh-credentials-local, with optional field-level AES-256-GCM encryption.
Install & usage
import { MysqlCredentialProvider } from '@sandersyao/dsh-credentials-mysql'
await ctx.plugin(MysqlCredentialProvider, {
connection: { tablePrefix: process.env.CREDENTIALS_TABLE_PREFIX },
})
// ctx.credentials is now backed by the MySQL vault.
Guides
- Try it in a dsh profile without touching existing credentials —
docs/DSH_PROFILE_TRIAL.md. - Production / npm install &
cordis.patch.ymlintegration (replace the default file provider) —docs/DEPLOYMENT.md§8.
Configuration
Credentials, table prefix and the encryption key come from environment variables / a .env file (see .env.example). Independent CREDENTIALS_* win; they fall back to the shared MYSQL_* — reuse the same connection when co-existing with dsh-session-persistence-mysql, or configure independently. The plugin Config is fully optional — environment is the source of truth for credentials (never hard-code a password).
| Env | Fallback | Default | Purpose |
|---|---|---|---|
CREDENTIALS_HOST | MYSQL_HOST | 127.0.0.1 | MySQL host. |
CREDENTIALS_PORT | MYSQL_PORT | 3306 | Port. |
CREDENTIALS_USER | MYSQL_USER | — (required) | Least-privilege DB user. |
CREDENTIALS_PASSWORD | MYSQL_PASSWORD | — (required) | Password. |
CREDENTIALS_DATABASE | MYSQL_DATABASE | — (required) | Target database. |
CREDENTIALS_TABLE_PREFIX | MYSQL_TABLE_PREFIX | — (required) | Table prefix; validated against ^[A-Za-z0-9_]+$; base names distinct from session tables to avoid collision. |
CREDENTIALS_ENCRYPTION_KEY | ENCRYPTION_KEY | (empty) | Field-encryption key; empty = plaintext (startup warning). |
CREDENTIALS_SSL_REQUIRED | MYSQL_SSL_REQUIRED | false | Reserved for TLS enforcement (deferred). |
CREDENTIALS_POOL_SIZE | MYSQL_POOL_SIZE | 10 | Pool sizing. |
CREDENTIALS_SCHEMA_AUTO_MIGRATE | MYSQL_SCHEMA_AUTO_MIGRATE | true | Auto-migrate schema on startup; false only validates. |
Test isolation. Automated tests (
vitest) run against a separate database to avoid touching the production one:CREDENTIALS_TEST_DATABASE(defaulttest) overridesCREDENTIALS_DATABASEduring tests, andMYSQL_ROOT_PASSWORDis used only by the test harness to create/grant the test DB. Seedocs/MANUAL_TEST_PLAN.md.
Storage layout
Three tables, all under CREDENTIALS_TABLE_PREFIX:
${prefix}credential_refs— the refs space:ref_name(PK) +value.${prefix}credential_records— the records space:rec_key(<scope>/<id>, PK) +kind+payload(JSON).${prefix}credential_meta— applied schema version.
The base names deliberately differ from dsh-session-persistence-mysql's sessions / events / _meta, so even sharing a database and prefix causes no collision.
Resolution layering (contract-equivalent to dsh-credentials-local)
inherited process environment (read-only, always wins)
> MySQL managed store (writable)
> project .env → user .env
- An empty stored value equals absent: empty strings are rejected on write;
resolveskips anddescribereports unconfigured. - Shadowing rule:
set/unsetreject explicitly while a read-only process environment supplies the ref;describe().writableisfalse. - Per-invocation env overrides represent this run's intent; a MySQL write takes effect immediately.
Concurrency & crash semantics
modifyRecordis mutually exclusive across processes:SELECT … FOR UPDATE+ an InnoDB transaction implements read-decide-replace, so concurrent token refresh is safe — a structural advantage over the file provider's cross-process write lock.- Transactional atomicity: writes commit in a single transaction, so no torn rows;
ER_LOCK_DEADLOCK(1213) retries with bounded backoff. - Crash safety: InnoDB guarantees committed writes are not lost.
Schema & migration
Startup performs a connection test + idempotent CREATE TABLE IF NOT EXISTS, then reads ${prefix}credential_meta; an applied version higher than expected fails closed (downgrade unsupported). With CREDENTIALS_SCHEMA_AUTO_MIGRATE=false, a version mismatch fails instead of migrating.
Field encryption (vault feature)
When CREDENTIALS_ENCRYPTION_KEY is set (32-byte hex or any string, key derived via SHA-256):
- ref values and a record's
key/env/payloadare AES-256-GCM encrypted before write (per-row random IV + auth tag); the key is never stored in the DB and never logged. - Storage uses a versioned envelope string (
v1:<iv>.<cipher+tag>); plaintext data is unaffected. - Without a key it's plaintext mode (startup warning); the switch does not change the seam's behavior contract (values still round-trip).
Model experience
Indirect, through the LLM adapters that consume it: a resolved value authorizes an adapter's request to its provider; all model-visible surfaces are the adapter's responsibility. Credentials never enter the request prefix.
Known limitations & deferred items
- No hot-publishing of external edits — no file watcher; rows changed directly in MySQL are picked up by consumers' per-operation re-resolution (the seam already resolves per operation, so this is usually invisible).
set/unsetreject while shadowed by the environment (seam rule, same as the local provider).- No automatic migration from
$DSH_HOME/.credentials.yaml— switching providers does not import the old file into MySQL (seedocs/DEPLOYMENT.md§8.3). - TLS / transport deferred —
CREDENTIALS_SSL_REQUIREDis a reserved bit. - Peers aligned to
^0.1.1-rc.2— officialv0.1.2-alpha.3compatibility is follow-up work; high coverage is the safety net.