# dsh-tool-encoding [中文](README.md) DSH encoding/hash tool plugin — base64/base64url/url/hex encode/decode + hash + UUID for UTF-8 text. Zero dependencies, zero subprocesses, pure functions. > Package name: `@deepseek-ai/dsh-tool-encoding` (standalone bundle, not a monorepo-integrated form); the `lib/` output is generated by `npm run build` (tsc) inside the repo and committed with the repository. [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ## Motivation Encoding/hashing is a high-frequency daily operation for agents: inspecting base64 fields in API responses (JWT payload), building query parameters, verifying file integrity (sha256), generating UUIDs. Problems with the current approach `bash -c "echo ... | base64"`: subprocess overhead, **quote-escaping hell** (a base64 string embedded in a bash command then in a JSON argument — the double-escaping error rate is extremely high), and inconsistent cross-platform tool names (`md5` vs `md5sum` vs `shasum -a 256`). ## Security model No `eval`, no `new Function`. All operations are pure-function compositions of `Buffer`/`node:crypto`/`TextDecoder`/`encodeURIComponent`: - **UTF-8 integrity**: decoding uses fatal mode (`TextDecoder('utf-8', { fatal: true })`); illegal bytes throw `encoding: invalid UTF-8 output`; legal U+FFFD/control characters are not rejected (`00` NUL, `0a` newline, `efbfbd` U+FFFD are all legal, `ff` is illegal) - **Strict base64 validation**: no whitespace, `=` only at the end and ≤2, length must be a multiple of 4, **RFC 4648 canonical unused bits** (non-canonical encodings such as `Zh==` are rejected, preventing multiple strings from mapping to the same text) - **Unified rejection of lone surrogates**: all text inputs pass through `String.prototype.isWellFormed()`, avoiding silent replacement and inconsistent URIError behavior - **Byte limits**: 1 MB input / 4 MB output (1,000,000 / 4,000,000 bytes each, `Buffer.byteLength`; the output limit is a fuse of pre-allocation estimate + final check) - **Hash algorithm whitelist**: `Object.hasOwn` lookup table (md5/sha1/sha256/sha512) - Errors uniformly use the `encoding:` prefix and never pass through the underlying `URIError`/`TypeError` ## Architecture ``` DSH Agent │ ctx.tools.register() ▼ src/index.ts(Cordis 插件入口 + action 分发 + 独立校验) │ ▼ src/encoding.ts ├── b64Encode/b64Decode — 标准 base64(严格校验) ├── b64UrlEncode/b64UrlDecode — base64url(canonical 无 padding,解码兼容) ├── urlEncode/urlDecode — component 语义(URIError 包装) ├── hexEncode/hexDecode — UTF-8 字节 hex(fatal 解码) ├── digest — 白名单哈希 ├── newUuid — crypto.randomUUID() └── validateUnicode/assertInputBytes/decodeUtf8Strict — 校验器 ``` ## Tool declaration ```ts ctx.tools.register(defineTool({ name: 'encoding', parameters: { action: { type: 'string', required: true, enum: ['base64_encode','base64_decode','base64url_encode','base64url_decode', 'url_encode','url_decode','hex_encode','hex_decode','hash','uuid'], }, input: { type: 'string', description: 'Input string for encode/decode/hash' }, algorithm: { type: 'string', enum: ['md5','sha1','sha256','sha512'], description: 'For hash' }, }, output: { schema: { type: 'json' }, render: (_a, v) => [{ type: 'text', text: JSON.stringify(v) }] }, execute: (args) => Promise.resolve(executeAction(args.action, args) as JsonValue), timeoutMs: 1000, })) ``` ## Supported operations | action | description | example | |--------|------|------| | `base64_encode` / `base64_decode` | RFC 4648 standard base64 (strict validation) | `"foobar"` → `"Zm9vYmFy"` | | `base64url_encode` / `base64url_decode` | JWT-style, unpadded output, decoding accepts `+/` and optional padding | `"\uFEFF"` → `"77u_"` | | `url_encode` / `url_decode` | **component semantics** (not full query): space is `%20` not `+`, `!'()*` not escaped, `decode("+")` → `"+"` | `"a b"` → `"a%20b"` | | `hex_encode` / `hex_decode` | UTF-8 byte hex; the decoded result must be valid UTF-8 | `"AB"` → `"4142"` | | `hash` | md5/sha1/sha256/sha512 hex digest; **non-security use only** | `sha256("")` → `e3b0c442...` | | `uuid` | UUID v4 string (`crypto.randomUUID()`) | `"550e8400-..."` | Semantic contract: - **v1 is a UTF-8 text tool**: all inputs and outputs are strings; binary content is represented as hex; decoded results must be valid UTF-8 - **All actions return strings** (including `uuid`) - **Do not use this tool on confidential material**: tool arguments are recorded in session logs ## npm 0.1.0-rc.8 compatibility (verified) This plugin has been migrated to the npm 0.1.0-rc.8 dependency line and fully verified in an isolated consumer of `@deepseek-ai/dsh@0.1.0-rc.8`: - **Types/runtime**: `@deepseek-ai/cordis@^4.0.1` + `@deepseek-ai/dsh-tools@>=0.0.1-rc.1 <0.2.0` + `@deepseek-ai/dsh-invariants@>=0.0.1-rc.1 <0.2.0` (peer); no longer depends on unscoped `cordis` - **Standalone build**: `npm install` (devDependencies are self-contained: typescript/vitest/@types/node) → `npm run typecheck` → `npm test` → `npm run build` → `npm pack` - **Consumption verification**: tarball installed into the 0.1.0-rc.8 consumer → `dsh --profile compat --dump-config` shows this plugin's row → the tool actually registers and executes - **Startup**: `npx -p @deepseek-ai/dsh@0.1.0-rc.8 dsh web` (lib production mode; do not `install -g` globally) ## Version adaptation - **DSH version adapted**: DSH 0.1.0-rc.8 (npm) - **Bundle declaration**: `dsh.bundle` in `package.json` (patch points to `cordis.patch.yml`) + `exports` fields - **Patch format**: `cordis.patch.yml` uses the `- insert:` list (patches are id-targeted; a bare `- id:` entry reports `entry not found`) - **files**: the published tarball contains `lib/`, `src/`, `cordis.patch.yml` ## Installation Plugin source repository: `https://github.com/omdsh-dev/dsh-tool-encoding` (public). ### Profile Bundle (recommended) Install this plugin as a standalone bundle into a profile (DSH 0.1.0-rc.8, npm): ```sh # 交互式(web)profile dsh plugin --profile web add github:omdsh-dev/dsh-tool-encoding # 一次性任务(headless)profile —— dsh run 默认使用 headless dsh plugin --profile headless add github:omdsh-dev/dsh-tool-encoding ``` The `dsh.bundle.patch` inside the package (pointing to `cordis.patch.yml`) automatically adds the plugin to the profile's layer stack after installation; the plugin's `cordis.patch.yml` inserts the `tool-encoding` entry via `- insert:`. > ⚠️ web and headless are **different profiles**: installing into web does not automatically cover headless; `dsh run` uses the headless profile by default. ### Install via npm pack tarball ```sh npm pack # generates dsh-tool-encoding-*.tgz dsh plugin --profile web add ./dsh-tool-encoding-*.tgz dsh plugin --profile headless add ./dsh-tool-encoding-*.tgz ``` ### Verify installation ```sh dsh --profile web --dump-config | grep tool-encoding ``` ### Run verification ```sh dsh run "使用 encoding 工具把 hello 做 base64 编码" ``` ### Manual installation and legacy compatibility Only for legacy snapshots that do not support Profile Bundle, or plugin development/debugging environments: 1. Place into the monorepo: `cp -r encoding ~/.dsh/source/master/packages/tools/encoding` (development/debugging) 2. Add `"@deepseek-ai/dsh-tool-encoding": "workspace:^"` to `apps/cli/package.json`; add `{ "path": "./packages/tools/encoding" }` to the `references` of `tsconfig.host.json` 3. `pnpm install && pnpm run build` 4. Insert the plugin in the profile's user-layer patch (`~/.dsh/profiles//cordis.patch.yml`): ```yaml - insert: - id: tool-encoding name: '@deepseek-ai/dsh-tool-encoding' ``` 5. Verify: `dsh --profile --dump-config | grep tool-encoding` > Note: patches are id-targeted — a bare `- id:` entry reports `entry "xxx" not found`; it must be wrapped in an `- insert:` list. ## Known limitations 1. Arbitrary binary (non-printable bytes) encode/decode requires v2's `output: "utf8" | "hex"` mode 2. `hash` provides digests only, no HMAC/salting/key derivation; MD5/SHA-1 are for compatibility/non-security integrity checks only 3. URL uses component semantics; form encoding (space → `+`) needs a separate action (v2) ## Testing ```bash pnpm test ``` 41 test cases covering functionality/errors/attack payloads (RFC 4648/1321/6234 known vectors, UTF-8 boundary distinctions, canonical padding boundaries, unified surrogate rejection, etc.). See the locally maintained design document for the full list. ## License MIT