Re-validates Cloudflare Access JWTs at the DSH origin so Settings, Credentials, Agent Preset management, and model discovery work from a remote hostname.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-cloudflare-access
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Luawig/dsh-cloudflare-access
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Cloudflare Access JWT verification and remote privileged authorization for DeepSeek Harness.
This is a dual-face Profile Bundle + Web Client plugin. It does not replace Cloudflare Access. Identity stays in Access. The plugin re-validates Cf-Access-Jwt-Assertion at the DSH Origin so Settings, Credentials, Agent Preset management, and model discovery can work from a remote hostname such as https://dsh.example.com.
It does not ship a login page, password store, MFA, session table, or Cloudflare API client.
Plugin 2.0.x targets DeepSeek Harness 0.1.5-alpha.1. Plugin 1.0.x is the line before DSH 0.1.2 (live-tested on 0.1.1-rc.2). Do not assume other DSH releases work until the compatibility matrix is updated.
Architecture
The plugin sits inside the DSH process. It does not replace Cloudflare Access or the Origin allowlist.
Internet → Cloudflare Access → Cloudflare Proxy
→ Origin allowlist → reverse proxy → DSH → this plugin
The Web client only enables capability (connection.isLoopback). Authorization stays on the Origin server.
Interactive figures (theme switch, guided views, SVG/PNG export): deployment, request path, client vs server, config. Notes: docs/diagrams.md.
Quick start
- Use a DeepSeek Harness Web profile. Pin
--trusted-hostto the public hostname. - Put that origin behind a Cloudflare Access application.
- Install and restart:
dsh plugin --profile web add dsh-cloudflare-access
- Lock the trust root (recommended in production):
export DSH_CF_ACCESS_TEAM_DOMAIN=https://example.cloudflareaccess.com
export DSH_CF_ACCESS_AUDIENCES=your-access-application-aud
- Open the site through Access and hard-refresh once, then load Settings. A valid
Cf-Access-Jwt-Assertionat Origin replaces DSH's launch-token cookie for remote Hosts. Loopback still needs the?token=URL printed bydsh web.
Confirm the bundle with dsh --profile web --dump-config: a layer named dsh-cloudflare-access and a plugin row id: cloudflare-access.
Security model
Remote privileged requests must pass both:
- DSH Host / Origin /
sec-fetch-sitechecks (--trusted-hostremains mandatory). - A valid Cloudflare Access JWT (signature,
iss,aud, expiry; about 30 seconds of clock skew is allowed).
A valid JWT never authorizes an arbitrary Host or Origin. Loopback (localhost / 127.0.0.1 / ::1) does not require a JWT, so SSH Tunnel → localhost → DSH keeps working. Remote privileged APIs always require a JWT in v2.0.
Keep Origin reachable only from Cloudflare (or equivalent ingress). Installing this plugin is not a reason to put DSH on the public internet. Details: SECURITY.md.
Host/Origin runs first. A valid JWT never rewrites Host to loopback. Privileged success goes to the original /api handler (DSH Remote). On a remote trusted host, a valid Access JWT also skips DSH's launch-token cookie. Missing or invalid JWT returns 401/403 and never enters the privileged implementation. Loopback does not read JWT and still uses the official DSH token/cookie.
Forward Host, Origin, and Cf-Access-Jwt-Assertion. Do not strip the assertion header. Do not trust the CF_Authorization cookie.
Install
Requires a Web profile. The package is on npm.
dsh plugin --profile web add dsh-cloudflare-access
Unreleased Git commit:
dsh plugin --profile web add github:Luawig/dsh-cloudflare-access
The Git tree ships prebuilt lib/index.js and lib/client.js. A github: install uses those artifacts and does not need TypeScript or esbuild.
Restart DSH. You do not edit $DSH_HOME/profiles/web/cordis.patch.yml by hand, and you do not patch DSH itself.
Uninstall:
dsh plugin --profile web remove dsh-cloudflare-access
After unload, this plugin's JWT wrap is gone. Restart if the running process still has the old fiber.
Configure
- Put the DSH Web origin behind a Cloudflare Access application.
- Copy the application Audience (
aud) and team domain, for examplehttps://example.cloudflareaccess.com(a host withouthttps://is accepted). - Cloudflare injects
Cf-Access-Jwt-Assertionon authenticated requests. That header is the only identity this plugin reads.
Issuer and JWKS URL are derived after the team domain is normalized to an http(s) origin (path is dropped):
issuer = <origin>
JWKS = <origin>/cdn-cgi/access/certs
You do not configure issuer or jwksUrl. Multiple audiences are supported.
Pin team domain and audiences with environment variables in production. If a variable exists, even as an empty string, that field is locked and cannot be retargeted from a remote Settings session.
| Variable | Meaning |
|---|---|
DSH_CF_ACCESS_TEAM_DOMAIN |
Team domain / issuer |
DSH_CF_ACCESS_AUDIENCES |
Comma-separated audiences |
DSH_CF_ACCESS_ORDINARY_MODE |
off | optional | required |
dsh --profile web --trusted-host dsh.example.com
systemd:
[Service]
Environment=DSH_CF_ACCESS_TEAM_DOMAIN=https://example.cloudflareaccess.com
Environment=DSH_CF_ACCESS_AUDIENCES=your-access-application-aud
Environment=DSH_CF_ACCESS_ORDINARY_MODE=off
ExecStart=/usr/bin/dsh --profile web --trusted-host dsh.example.com
Cordis overlay (only used when the matching env var is unset):
cloudflare:
teamDomain: https://example.cloudflareaccess.com
audiences:
- your-access-application-aud
auth:
ordinary: off
Missing teamDomain or audiences: the plugin still starts, loopback is unchanged, remote privileged APIs are denied.
Ordinary API modes
auth.ordinary applies only to remote non-privileged APIs, including /api/remote.mux. Loopback ignores it. Host/Origin always runs first.
| Mode | No JWT | Valid JWT | Invalid JWT |
|---|---|---|---|
off (default) |
DSH original policy | ignored | ignored |
optional |
DSH original policy | allow | deny |
required |
deny | allow | deny |
Privileged remote APIs always require a valid JWT, regardless of this setting.
Troubleshooting
| Symptom | Check |
|---|---|
| Remote Settings still unavailable | Access must sit in front of the site; hard-refresh so the client module loads before Settings; confirm Cf-Access-Jwt-Assertion reaches Origin. |
Settings UI never calls settings/describe |
This package sets dsh.client.immediately: true. Reinstall if an older tarball omitted that. |
401 on settings/* |
Access header missing or not forwarded. Inspect reverse-proxy forwarding of Cf-Access-Jwt-Assertion. Loopback still needs the ?token= URL printed by dsh web. |
403 on settings/* |
Invalid iss/aud/signature/expiry, unconfigured plugin, Host/Origin mismatch, or Origin clock more than ~30s off. |
Events WebSocket fails when ordinary=required |
/api/remote.mux follows the ordinary policy. Missing JWT → 401; invalid JWT → 403. |
| Loopback Settings broken | Unload the plugin; loopback must not require JWT. File a bug if it does. |
| JWKS / key rotation failures | Origin must reach https://<team>/cdn-cgi/access/certs. No config change after Cloudflare rotates keys. |
| Logs | Categories only (expired, invalid_signature, issuer_mismatch, audience_mismatch, missing_token, jwks_unavailable, unconfigured). Tokens are never logged. |
This plugin does not authorize host.pickDirectory or host.openPath. Some native-host UI may still appear.
Compatibility
| Plugin | DSH | Status |
|---|---|---|
| 1.0.x | 0.1.1-rc.2(0.1.2 之前) | Live-tested (Web profile, remote Settings / Credentials). apiProxy + privileged pin. Not compatible with DSH 0.1.2+. |
| 2.0.x | 0.1.5-alpha.1 | Live-tested on a Web profile behind Cloudflare Access (remote Settings without DSH ?token=). Unit/integration tests. CI does not start a DSH process. |
Do not assume newer DSH releases work until this matrix is updated.
Develop
pnpm install
pnpm test
pnpm typecheck
pnpm pack:check
After source changes, run pnpm build so committed lib/ matches src/. CI rebuilds lib/ and fails if the tree drifts.
dsh plugin --profile web add ./
Plugin packaging follows dsh.pub/develop-plugin.md. This repository is not claiming a dsh.pub listing or a security audit.
Maintainers
Dependabot updates npm and GitHub Actions weekly. When publishing from GitHub Actions, use npm publish --access public --provenance with id-token: write. v0.1.0 and v1.0.0 were published without provenance.
License
MIT
Links
More in this category
toby-bridges/api-relay-audit★ 882
Runs local security audits of AI API relays and LLM proxies from DeepSeek Harness, producing Markdown reports for prompt injection, model substitution signals, tool-call rewriting, error leakage, stream integrity, and profile-gated Web3 risks.
SeaOf0/dsh-redteam-model★ 711
Authorized-security DSH collection: nine work modes (redteam coordinator, pentest, code audit, binary analysis, attack-defense, AV evasion, incident response, cloud security, CTF solving) and fifteen runtime plugins, managed from a settings page with one-click deploy, install, update and uninstall.
howmp/dsh-pentest★ 609
Authorized pentest mode for DeepSeek Harness — exploration chain, assets and findings with a Web view.
PerryLink/dsh-auto-review★ 232
Second-model auto-review on the approval answerer chain: a read-only reviewer subagent returns structured allow/deny verdicts with reasons, fail-closed by default.
ADWMC/helm-d#helmd★ 219
Single-bundle reverse-engineering and pentest security plugin: first-turn tool narrowing, domain routing, and 33 tools covering APK, web, native binary, protocol, malware and LLM samples — with unpacking, license-bypass and anti-analysis case playbooks, an H-CoT evaluation engine (semantic routing, /hcot command), a web workbench with a ledger-driven tool shelf, an on-disk case workflow (auto-persisted evidence chain, E-numbered validated findings, post-compaction resume), GitHub-based external tool discovery, and 361 on-demand reference docs.
NanmiCoder/dsh-auto-mode★ 164
Adds an Auto permission preset between Workspace Write and Full access: routine work stays in the official workspace-write sandbox while the current session model reviews escalation and destructive calls, granting one exact wider access once, asking when the intent is ambiguous, and denying critical paths.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.