Skip to content
dsh.fish
Bundle

@sandersyao/dsh-credentials-mysql

MySQL-backed credentials vault provider for the DeepSeek Harness (ctx.credentials)

Source
sandersyao
stars
1 stars
License
MIT
Updated
Updated yesterday

Readme

# @sandersyao/dsh-credentials-mysql

<p align="center">
  <img src="assets/dolphin_safe_cartoon.jpg" alt="A cartoon dolphin tapping away at a typewriter" width="480" />
</p>

English | [中文](README.zh.md)

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**.

## Companion plugins (distributed dsh deployment)

This MySQL vault is the credentials component of a **shared-MySQL distributed dsh
deployment**, designed to run alongside three sibling plugins — `dsh-workspace-bootstrap`,
`dsh-storage-mysql`, and `dsh-session-persistence-mysql` — which switch the default
workspace / storage / session-persistence backends to shared MySQL (this plugin switches
the credentials backend):

| Plugin | GitHub repository | npm package page |
| --- | --- | --- |
| `@sandersyao/dsh-workspace-bootstrap` | https://github.com/sandersyao/dsh-workspace-bootstrap | https://www.npmjs.com/package/@sandersyao/dsh-workspace-bootstrap |
| `@sandersyao/dsh-storage-mysql` | https://github.com/sandersyao/dsh-storage-mysql | https://www.npmjs.com/package/@sandersyao/dsh-storage-mysql |
| `@sandersyao/dsh-session-persistence-mysql` | https://github.com/sandersyao/dsh-session-persistence-mysql | https://www.npmjs.com/package/@sandersyao/dsh-session-persistence-mysql |

## Install & usage

```ts
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.yml` integration (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` (default `test`) overrides `CREDENTIALS_DATABASE` during tests, and `MYSQL_ROOT_PASSWORD` is used only by the test harness to create/grant the test DB. See `docs/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)

```text
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; `resolve` skips and `describe` reports unconfigured.
- **Shadowing rule**: `set`/`unset` reject explicitly while a read-only process environment supplies the ref; `describe().writable` is `false`.
- Per-invocation env overrides represent this run's intent; a MySQL write takes effect immediately.

## Concurrency & crash semantics

- **`modifyRecord` is 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` / `payload` are **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`/`unset` reject 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 (see `docs/DEPLOYMENT.md` §8.3).
- **TLS / transport deferred** — `CREDENTIALS_SSL_REQUIRED` is a reserved bit.
- **Peer ranges target the dsh `v0.1.2-rc.1` line** (`^0.1.2-rc.1`); re-align `peerDependencies` when the official seam release moves on.

Install

dsh plugin --profile web add github:sandersyao/dsh-credentials-mysql

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source