
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
dsh-git-credentials
Advanced tools
Out-of-tree dsh plugin: GitLab, GitHub, Gitee, Gitea, and Bitbucket tokens stay out of the model context, stored encrypted (AES-256-GCM) in a plugin-owned file; the model calls forge API tools on demand, and the web settings page manages sites and tokens.
An out-of-tree plugin for DeepSeek Harness that manages GitLab, GitHub, Gitee, Gitea, and Bitbucket API tokens so token values never enter the model context.
The model's tools carry only a token reference name (e.g. GITLAB_TOKEN); the value is decrypted from the plugin's own encrypted store at call time and appears only in the outgoing HTTP Authorization header. Changing a site or rotating a token takes effect on the very next call — no restart required.
site, project, path, …) crosses the model boundary0600, atomic writes)gitlab_* tools only see GitLab sites, github_* only GitHub sites, and likewise for gitee_*, gitea_*, bitbucket_*; unconfigured sites/tokens fail loud with the valid values listed in the errorGitHub publishes an official MCP server, and the harness supports MCP clients natively — for GitHub-only automation, wiring up the official MCP server is the mainstream choice, and this plugin's github_* tools do overlap with it.
This plugin earns its place where MCP servers don't cover the gap:
| Official GitHub MCP | This plugin | |
|---|---|---|
| Forges | GitHub only (GitLab has an official server; Gitee / Gitea / Bitbucket rely on third-party servers of varying quality and maintenance) | One encrypted store, one settings panel, one tool set for GitLab, GitHub, Gitee, Gitea, and Bitbucket — including self-hosted Gitea / GitLab |
| Token handling | Plaintext environment variables per server, no management UI | AES-256-GCM encrypted storage, token reference names, settings-page management; token values never enter the model context |
| Integration | Extra MCP proxy process | Tools register directly in the harness tool registry |
Use the MCP route for a single hosted forge with standard token handling; use this plugin for multi-forge setups (especially Gitee or self-hosted Gitea), or when you want encrypted storage plus an in-product management page.
~/.dsh/git-credentials.json — data file, fully encrypted with AES-256-GCM (0600, atomic write)~/.dsh/git-credentials.key — 32-byte random key, stored as a separate file (0600)| Scenario | Protected? |
|---|---|
| A human copies/backs up/syncs the data file | ✅ Yes — ciphertext only; without the key file it cannot be decrypted |
| A same-UID process (e.g. the agent's bash/fs tools) reads both files | ❌ No — the key sits beside the data with the same permissions; same level as the harness's own key handling ("discretion, not a boundary") |
| The user deliberately asks the model to read the files | ❌ No — out of scope; no system can stop that |
Losing the key file means the data is unrecoverable (decryption fails loud and reports the key path); a copied data file alone is safe.
Download dsh-git-credentials-<version>.tgz from the releases page — the tarball ships the built browser bundle, so no harness checkout or build step is needed — then install it into a profile with the dsh CLI:
dsh plugin --profile <name> add ./dsh-git-credentials-0.3.3.tgz
The first use initializes the profile, pnpm links the package, and dsh appends the plugin to the profile's bundle layers. Verify the layer without booting:
dsh --profile <name> --dump-config # look for "# == dsh-git-credentials"
Installing a bundle does not hot-mount into a running GUI: bundle layers are composed at boot (HMR hot-applies only patch files), so restart the GUI process after
dsh plugin add. After the restart, the plugin appears under Settings → Git Credentials.
The plugin is a pure add-on — zero changes to harness code. Two entries under ~/.dsh are enough:
Symlink the plugin directory so every profile can resolve the package:
mkdir -p ~/.dsh/profiles/node_modules
ln -s /path/to/dsh-git-credentials ~/.dsh/profiles/node_modules/dsh-git-credentials
Add the plugin row to the home-layer overlay ~/.dsh/cordis.patch.yml (applies to every profile, web and headless alike):
- insert:
- id: git-credentials
name: 'dsh-git-credentials'
The HMR watcher monitors the home layer: adding the row hot-mounts the plugin into a running GUI, removing it (or disabled: true) hot-unmounts it, and config edits hot-reconfigure it. Uninstalling = removing both entries.
The browser half (
lib/client.js) is a build artifact — after cloning, build it first (see Development). The release tarball already contains it.
Manage sites and tokens in Settings → Git Credentials:
https://api.github.com, https://gitee.com/api/v5, https://api.bitbucket.org/2.0; GitLab and Gitea are self-hosted and need their own address, e.g. https://gitlab.example.com / https://gitea.example.com/api/v1), token reference name (defaults to GITLAB_TOKEN / GITHUB_TOKEN / GITEE_TOKEN / GITEA_TOKEN / BITBUCKET_TOKEN), optional token value (write it with the dedicated Save Token button, or together with Add Site), optional default project/git-credentials-admin/* JSON endpoints; token values never appear in any responseOne resource tool per provider, with an action parameter selecting the operation. All actions return the provider's canonical summary shapes (repos: {id, path, name, webUrl, visibility}; issues/PRs: {number|iid, title, state, webUrl, authorName}; file: {path, ref, content, truncated}). Write actions perform a real mutation — the model should confirm with the user before calling them.
| Tool | action | Parameters |
|---|---|---|
gitlab_projects | list, create | list: search?, membership?, perPage? · create: name, description?, path?, visibility? |
gitlab_file | read | project, path, ref? |
gitlab_merge_requests | list, create, merge, close | project?, state?, perPage?, number, title, sourceBranch, targetBranch, body? |
gitlab_issues | list, create, close, reopen, comment | project?, state?, perPage?, number, title, body? |
github_repos | list, create | list: search?, perPage? · create: name, description?, private? |
github_file | read | project (owner/repo), path, ref? |
github_pull_requests | list, create, merge, close | project?, state?, perPage?, number, title, head, base, body? |
github_issues | list, create, close, reopen, comment | project?, state?, perPage?, number, title, body? |
gitee_repos | list, create | list: search?, perPage? · create: name, description?, private? |
gitee_file | read | project (owner/repo), path, ref? |
gitee_pull_requests | list, create, merge, close | project?, state?, perPage?, number, title, head, base, body? |
gitee_issues | list, create, close, reopen, comment | project?, state?, perPage?, number, title, body? |
gitea_repos | list, create | list: search?, perPage? · create: name, description?, private? |
gitea_file | read | project (owner/repo), path, ref? |
gitea_pull_requests | list, create, merge, close | project?, state?, perPage?, number, title, head, base, body? |
gitea_issues | list, create, close, reopen, comment | project?, state?, perPage?, number, title, body? |
gitlab_releases | list, create, delete | project?, perPage?, tag (create requires; delete deletes by tag on GitLab), name?, body?, draft?, prerelease? |
github_releases | list, create, delete | project?, perPage?, tag, number (release id, required for delete), name?, body?, draft?, prerelease? |
gitee_releases | list, create, delete | project?, perPage?, tag, number (release id, required for delete), name?, body?, draft?, prerelease? |
gitea_releases | list, create, delete | project?, perPage?, tag, number (release id, required for delete), name?, body?, draft?, prerelease? |
bitbucket_repos | list, create | list: search?, perPage? · create: name, description?, private? |
bitbucket_file | read | project (workspace/repo), path, ref? |
bitbucket_pull_requests | list, create, merge, close | project?, state?, perPage?, number, title, head, base, body? |
bitbucket_issues | list, create, close, reopen, comment | project?, state?, perPage?, number, title, body? |
action defaults to the read operation (list, or read for file) — existing read callers keep working unchangednumber is the issue/PR number (iid on GitLab); required for close / reopen / comment / mergestate values: GitLab opened/closed/all (merged for MRs), the others open/closed/allfile always reads: project, path, ref? (defaults to the repository default branch; content over the byte cap is truncated and flagged)bitbucket_repos create needs the site's defaultProject (workspace/repo) to know which workspace to create inbitbucket_releases tool; release delete uses the release number (GitLab deletes by tag)GITLAB_TOKEN, GITHUB_TOKEN, GITEE_TOKEN, GITEA_TOKEN, BITBUCKET_TOKEN, …); multiple sites can share one reference or use their ownPRIVATE-TOKEN header; GitHub, Gitee, and Bitbucket with Authorization: Bearer (Gitee additionally falls back to the access_token URL parameter when the header form is rejected); Gitea with Authorization: tokenfetch directly — ctx.web.fetch is deliberately not used (URL-only, no header support)~/.dsh/git-credentials.json (AES-256-GCM encrypted: sites + token values)
→ tool execution decrypts one snapshot, filters sites by provider, resolves tokenRef
→ fetch(baseUrl/<provider api path>, { headers: { PRIVATE-TOKEN | Bearer | token } })
→ tool arguments/returns/errors carry only business data (site, project, path, …)
Prerequisites: a clone of deepseek-harness. The dev toolchain is harness-backed: point DSH_REPO at the checkout and put its node_modules/.bin on PATH (the harness's @deepseek-ai/* packages are private and resolve through its tsconfig paths).
The browser half targets the current client slot standard: the panel is a settings.section list entry whose component receives the composed section props, and the typecheck program pulls the slot contracts through type-only imports. Typecheck against the checkout you actually run — regenerate tsconfig.json after switching harness versions.
Harness compatibility is enforced from the manifest. DSH reads this package's peerDependencies on @deepseek-ai/dsh and @deepseek-ai/dsh-* and refuses to apply the bundle layer unless every range matches the running harness version (prereleases included); a refused bundle contributes no tools and no settings page, and the harness reports the refused peers. The @deepseek-ai/dsh-tools range is therefore the one to bump when adapting to a new harness generation (harness packages version in lockstep with the product). Verify a package against a checkout without installing it — the check itself takes only the manifest:
node --input-type=module -e "
import { readFileSync } from 'node:fs'
const { evaluatePluginCompatibility, pluginCompatibilityWarning } = await import('$DSH_REPO/packages/boot/app-boot/lib/index.js')
const manifest = JSON.parse(readFileSync('./package.json', 'utf8'))
const issue = evaluatePluginCompatibility(manifest)
console.log(issue === undefined ? 'compatible' : pluginCompatibilityWarning(issue))
"
export DSH_REPO=/path/to/deepseek-harness
export PATH="$DSH_REPO/node_modules/.bin:$PATH"
# Regenerate tsconfig.json paths for this checkout (gitignored — machine-specific)
pnpm gen:tsconfig
# Typecheck (including the browser half)
pnpm typecheck
# Keyless smoke: encrypted-store round-trip + boot assertions + loud failure
# for unconfigured tokens (no network, no model key)
DSH_REPO="$DSH_REPO" TSX_TSCONFIG_PATH="$DSH_REPO/tsconfig.json" \
node --import "$DSH_REPO/node_modules/tsx/dist/esm/index.mjs" smoke.ts
# Rebuild the browser bundle after touching src/client/ (the live GUI hot-swaps it)
pnpm build
lib/index.js, host-side edits need a pnpm build before the restart; or rename the plugin directory so module URLs change and hot-swap zero-restart$DSH_REPO/node_modules/.bin/tsdown is a shell shim — run it directly (as pnpm build does), not via node .../.bin/tsdown.
git-credentials/
package.json # dsh-git-credentials; peers: @deepseek-ai/{cordis,dsh-tools,dsh-schemastery}
# dsh.client manifest + exports["./client"] (browser half)
cordis.patch.yml # bundle patch layer (dsh.bundle.patch) — also the dev --patch overlay
tsdown.config.ts # self-contained build (node half + browser bundle, no harness checkout)
smoke.ts # keyless boot smoke (incl. encrypted-store round-trip assertions)
tools/gen-tsconfig.mjs # regenerates tsconfig.json paths for this checkout (DSH_REPO-driven)
src/index.ts # plugin entry: 24 tool registrations + admin route wiring
src/store.ts # AES-256-GCM encrypted storage (independent key, atomic write, 0600)
src/http.ts # shared HTTP helpers (token resolution, pagination, error detail)
src/gitlab.ts # GitLabClient (PRIVATE-TOKEN header)
src/github.ts # GitHubClient (Bearer header + User-Agent)
src/gitee.ts # GiteeClient (Bearer header, access_token URL fallback)
src/gitea.ts # GiteaClient (token header)
src/bitbucket.ts # BitbucketClient (Bearer header, 2.0 API)
src/admin.ts # /git-credentials-admin/* management endpoints
src/invariant.ts # invariant companion (out-of-tree rationale)
src/client/ # browser half: the Settings → Git Credentials panel
lib/ # build output (node half + client bundle, gitignored)
The package is shaped as a dsh bundle: dsh.bundle.patch points at cordis.patch.yml, so users install it with dsh plugin --profile <name> add dsh-git-credentials and it joins the profile's bundle layers. The runtime resolves the plugin's @deepseek-ai/* imports from the installation's flat fallback ($DSH_HOME/profiles/node_modules), so the peerDependencies declare the published version line (@deepseek-ai/cordis ^4.0.1-rc.1, @deepseek-ai/dsh-tools ^0.1.7-rc.1, @deepseek-ai/schemastery ^3.18.1-rc.1) — never the dev-workspace 0.1.0-rc.5 versions. Harness packages version in lockstep with the product, and DSH admits a bundle only while those ranges match the running harness, so @deepseek-ai/dsh-tools is the line to bump when adapting to a new harness generation (see Development).
Two channels carry the same packed artifact:
npm — pnpm publish ships the built package to the public registry; publishConfig pins https://registry.npmjs.org/, so a mirror configured in the local npm client cannot misroute the publish. lib/ is gitignored but whitelisted in files, so build first:
pnpm build
pnpm publish
Users then install with dsh plugin --profile <name> add dsh-git-credentials.
GitHub release — pushing a v* tag triggers GitHub Actions to build the node half + browser bundle in the cloud, pack the tarball, and attach it to the release. The same pipeline, by hand:
pnpm build
pnpm pack # -> dsh-git-credentials-<version>.tgz
Attach that tarball to the release, or install it directly:
dsh plugin --profile <name> add ./dsh-git-credentials-<version>.tgz
Verify the artifact locally before announcing it — dsh plugin --profile <name> add <tarball|package>, confirm dsh --profile <name> --dump-config shows the # == dsh-git-credentials layer, then boot the profile and check the 24 tools register.
FAQs
Out-of-tree dsh plugin: GitLab, GitHub, Gitee, Gitea, and Bitbucket tokens stay out of the model context, stored encrypted (AES-256-GCM) in a plugin-owned file; the model calls forge API tools on demand, and the web settings page manages sites and tokens.
The npm package dsh-git-credentials receives a total of 0 weekly downloads. As such, dsh-git-credentials popularity was classified as not popular.
We found that dsh-git-credentials demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.