DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

frederico-kluser /

frederico-kluser/dsh-guard-messenger

Verified

Plugin Cordis para o DeepSeek Harness: controle remoto pelo Telegram — túnel autenticado, chats de agentes, worktrees e métricas de tarefas.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@43781683

dsh-guard-messenger

CI npm version npm downloads OpenSSF Scorecard

dsh-guard-messenger

Usa o teu próprio DeepSeek Harness pelo celular — a Web UI inteira, para codificar de verdade — sem nunca alargar o bind para fora do loopback: o túnel termina em 127.0.0.1 (acesso local abre direto), e pelo túnel só entra quem tem a chave no link ?key= (que o bot envia) — ligas e desligas o acesso pelo Telegram ou pelo Discord.

Demo

Instalação (uma linha)

dsh plugin --profile web add dsh-guard-messenger

Instalação por git / tarball: se instalares a partir do git (pnpm add <git-url>), o lifecycle prepare (pnpm run build:all) gera automaticamente lib/client.js e dist/ — o bundle que o harness monta (exports["./client"] → lib/client.js). Sem esses artefactos a ativação do client lançaria MissingClientBundleError. Ver docs/PANEL-TELEGRAM.md.

Modelo de ameaça, em 5 linhas — antes de qualquer feature

Este plugin expõe, por escolha, um agente que executa código na tua máquina. Antes de continuares, lê isto:

  1. O TLS termina na borda da Cloudflare. O texto claro (prompts, código, respostas) passa por um terceiro — é o que permite WAF/Access/cache. Não é ponta-a-ponta.
  2. A URL do túnel é pública e não é segredo. Quem protege é a chave ?key= no link, não a obscuridade do endereço.
  3. Cloudflare Access não pode ficar na frente de um quick tunnel. Sobre *.trycloudflare.com toda a autenticação tem de estar dentro da aplicação.
  4. O quick tunnel não tem SLA — é "intended for testing and development only".
  5. Isto não é "seguro por padrão sem pensar". Reduzimos a superfície, autenticámos a borda por chave/sessão e demos-te o botão de desligar; não eliminámos a categoria do risco.

Detalhe e o que cada mitigação faz: docs/THREAT-MODEL.md.

Modelo de segurança (o que o código garante de facto)

Leitura honesta das garantias — cada linha aponta para o código que a cumpre; nada aqui é "seguro por padrão sem pensar", é redução de superfície e autenticação.

Propriedade Como
O bind nunca é alargado assertSecureBind recusa 0.0.0.0/:: no carregamento, com falha ruidosa (src/config/bind.ts)
Acesso local abre direto em 127.0.0.1 o DSH responde sem barreira; a proteção aplica-se à superfície do túnel, não ao loopback (src/tunnel/proxy.ts)
Pelo túnel só entra com sessão ou chave no link o proxy autentica tudo: sessão (cookie) ou ?key= no link; fora disso → 401 sem desafio (src/http/gate.ts, src/session/link-token.ts)
Origem e Host vêm primeiro trustedRemotes (403 sem credencial) e Host byte-a-byte contra DNS rebinding (src/http/gate.ts:15, src/http/host-header.ts)
Chave no link reutilizável, revogável por rotação CSPRNG 256 bits; guardada só como digest SHA-256; reutilizável até /rotacionar (gera chave nova, invalida sessões e encerra as conexões ativas — WebSockets e streams em voo caem na hora) ou derrubar o túnel (src/session/link-token.ts, src/tunnel/proxy.ts)
O 401 é sem desafio texto puro, sem desafio de login (o popup do navegador foi removido) — nunca se pede senha em prompt/formulário (src/http/responses.ts, denyUnauthorized)
Força bruta tem teto a 5ª falha começa a atrasar; 100 falhas acumuladas derrubam a exposição (modo restrito), só o loopback passa e o reiniciar não o contorna (src/ratelimit/**)
Verificação da chave em tempo constante digest comparado com timingSafeEqual; o token redige-se em JSON/inspect (src/session/link-token.ts, test/security/timing-constante.test.ts)
danger-full-access vetado elevação proibida recusada como defesa em profundidade (src/permissions/deny.ts)
Só o dono pareado comanda o bot allowlist de dois eixos (userKey/chatKey), default deny (worker/surface/auth.ts)
O que NÃO se garante o TLS termina na borda da Cloudflare (texto claro passa por lá); a URL do túnel não é segredo; quem tiver o link acede até rotacionar; a ?key= viaja em query (visível a intermediários) — trade assumido pelo dono; prompt injection continua aceite — decisões de desenho em docs/THREAT-MODEL.md §4 e §5.1

A tensão central, dita por nós antes que digam por nós

O projeto foi construído para travar o DSH em loopback. Este plugin também o expõe pela internet. Parece contradição — e a formulação honesta é esta:

O bind continua em 127.0.0.1 e o DSH aí abre direto (sem login). O que muda é que passa a existir um processo filho supervisionado (cloudflared) que leva o tráfego da borda da Cloudflare até um proxy dedicado que autentica na borda (sessão ou ?key=). Não é o mesmo que --host 0.0.0.0: o socket local nunca é alargado, a exposição é opt-in, efémera e revogável em um comando — e quem proteger /api, o fallback da SPA e o handshake de WebSocket é esse proxy, não o loopback.

O que não muda: a superfície de ataque lógica cresce. Antes, um atacante precisava de acesso à máquina; agora, pelo túnel, precisa da chave no link (ou de uma sessão roubada). Trocámos "inalcançável" por "alcançável e autenticado na borda" — sem login, sem senha a digitar. Essa troca é reversível a qualquer momento pelo botão de desligar (e a chave, pelo /rotacionar).

Quem não aceitar esta troca deve usar Tailscale ou SSH — e dizemo-lo com mais calma em docs/TUNNEL.md e na secção "Quando NÃO usar" abaixo.

O que faz (e porquê)

  1. Protege o túnel, não o loopback. O DSH abre direto em 127.0.0.1 (sem login); quem expõe é um proxy dedicado que autentica tudo o que chega da internet — /api, o fallback da SPA e o handshake de WebSocket. Recusa endereços de bind fora do loopback no carregamento e recusa permissões proibidas (danger-full-access). Resolve a superfície da discussão upstream #853.
  2. Nunca pede senha a ninguém. O acesso pelo túnel entra por sessão ou pela chave no link ?key= (CSPRNG, 256 bits, digest em disco). A chave é reutilizável até /rotacionar (que gera chave nova, invalida sessões e encerra ativamente as conexões já abertas — quem tiver o link antigo cai na hora, incluindo WebSockets) ou derrubar o túnel. O 401 é sem desafio de login — não há prompt nem formulário de login.
  3. Suba um túnel efémero para acederes pelo celular, com TTL que o derruba sozinho e um probe fail-closed que impede um túnel "nu" (sem proxy autenticado atrás).
  4. Ligar/desligar pelo Telegram, Discord ou painel — o botão de matar na mão.
  5. Dispara agentes do harness pelo bot — com uma skill da allowlist e um prompt, o dono manda um subagente do DeepSeek Harness trabalhar na própria máquina (/agente), acompanha os runs (/agentes) e cancela (/parar-agente). O dispatch executa código no host: exige confirmação em duas etapas, e o agente corre com as permissões do harness — nunca com o token do bot. Manual completo: docs/AGENTS.md.

Bot: botão da UI e link automático

Na UI do DSH há o botão do bot (/__guard-ui), com estado OFFLINE/ONLINE fiel ao runtime:

  • OFFLINE → o clique mostra as instruções de conexão do provedor ativo (Telegram: criar o bot no @BotFather; Discord: criar a aplicação no Developer Portal — ver docs/ONBOARDING-DISCORD.md), dsh-guard-setup --pedir-token, --parear, enviar /parear <código>; quem segue esse passo a passo de facto coloca o bot online;
  • ONLINE → mostra dicas de uso.

Depois de pareado (docs/ONBOARDING-TELEGRAM.md para o Telegram, docs/ONBOARDING-DISCORD.md para o Discord — os comandos são os MESMOS nos dois provedores), os comandos de controlo do bot:

Comando O que faz
/ligar Sobe o túnel e, quando fica READY, envia automaticamente o link com a chave ?key=
/desligar Derruba o túnel (e revoga a chave)
/acessar (Re)envia o link com a chave
/rotacionar Gera chave nova, invalida as sessões e encerra as conexões ativas (WebSocket/streams) — revoga o acesso antigo na hora
/status · /emergencia estado, kill switch
/agente · /agentes · /parar-agente dispara, lista e cancela agentes do harness — ver abaixo

O link enviado no /ligar é https://<url-pública>/?key=<token>: a chave no link autentica o túnel, é reutilizável até rotacionar (ou derrubar o túnel) e, ao ser válida, é trocada por uma sessão e o navegador é redirecionado para a URL limpa (sem ?key=). Nunca se pede uma senha a digitar — o acesso é por sessão ou por chave no link, e o acesso local abre direto.

Porque a chave pode aparecer no chat (e é esperado): o ?key= é o mecanismo de autenticação e o bot é o canal de entrega. É a chave do link, não uma "senha permanente" — não há senha a digitar para acesso.

Cada promessa destas aponta para a linha de código que a cumpre: ver docs/ARCHITECTURE.md.

Agentes: disparar skills do harness pelo bot

O dispatcher de agentes (docs/AGENTS.md — manual completo) liga o bot ao ctx.subagents do harness: o dono escolhe uma skill da allowlist e escreve um prompt, o host spawna um subagente in-process (sessão fresca, zero contexto do pai) que trabalha na própria máquina, e o resultado chega ao chat.

Comando O que faz
/agente <skill> <o que o agente deve fazer> Abre a confirmação em 2 etapas e dispara o agente (o prompt mostrado é o que vai)
/agentes Lista os runs: • <id> — <skill> — <estado> <há quanto> (+ resumo do modelo)
/parar-agente <id> Cancela um run (os ids aparecem em /agentes)

Porquê tanta cerimónia: o dispatch executa código na tua máquina. É a ação que mais AUMENTA a exposição do bot — por isso exige confirmação em duas etapas (nonce do host, como /ligar). E o agente disparado corre com as permissões do harness e nunca recebe o token do bot nem credencial nenhuma deste plugin (S3): o request carrega só a skill renderizada no bloco <skill_content> canónico

  • o texto do teu prompt. Os runs são efémeros por desenho: vivem só em memória — um reinício do DSH cancela tudo (dispose em LIFO) e a lista recomeça vazia. Sem allowlist declarada (config.agents.skills), nenhum agente é disparável — fail-closed por construção.

Provedores de mensageria

O worker do bot é neutro ao provedor: o núcleo (roteador, allowlist, pareamento, outbox) vive em worker/surface/** e cada canal vive isolado no seu adaptador: worker/providers/telegram/** (única carga de grammY) e worker/providers/discord/** (gateway WebSocket próprio, sem SDK). O boot lê o provedor ativo por DSH_GUARD_PROVIDER (config.worker.provider, default telegram); o token é TELEGRAM_BOT_TOKEN no Telegram e DISCORD_BOT_TOKEN no Discord — o secrets.env guarda as duas linhas e dsh-guard-setup sabe qual é a do provedor ativo.

Adicionar um provedor novo (WhatsApp, Matrix…) é implementar o contrato neutro ProviderAdapter e registá-lo no registry. O manual completo — arquitetura, contrato tipo-a-tipo, a tabela de provedores com os limites de cada um e o checklist passo-a-passo para um provedor novo — está em docs/PROVIDERS.md, e as habilidades de apoio (skills) em .agents/skills/dsh-provider-bot e .agents/skills/dsh-telegram-provider.

Como flui um pedido (arquitetura em 8 linhas)

Telemóvel (navegador)
   │  abre o link que o bot enviou: https://<url>/<tunel>/?key=<token>   (a chave viaja em query)
   ▼
❨ Cloudflare edge ❩   TLS → HTTP/2 → WebSocket ; o TLS termina AQUI (texto claro passa pela borda)
   ▼
cloudflared — quick tunnel (conexão de saída apenas, sem conta, sem SLA)
   ▼  http://127.0.0.1:3080   (o túnel termina no loopback; o socket local NÃO é alargado)
plugin (proxy dedicado — só o túnel passa por aqui)
   │  autentica na borda: sessão (cookie) ou `?key=` válida → troca por sessão (302 p/ URL limpa)
   │  403 origem/ Host fora da allowlist · 401 SEM desafio (sem login) · 200 com sessão
   ▼
DeepSeek Harness Web UI — bind travado em 127.0.0.1 (nunca 0.0.0.0)

O que o diagrama esconde e é decisivo: o acesso local abre direto (sem barreira) — a autenticação vive só no proxy do túnel. A chave no link é gerada pela máquina (CSPRNG, 256 bits), guardada em disco só como digest, reutilizável até rotacionar, e a ?key= viaja em query (visível a intermediários) — trade assumido do modelo expose-port. A recusa de bind fora do loopback acontece no carregamento, com falha ruidosa — ver o mapa de módulos em docs/ARCHITECTURE.md.

Quickstart (5 comandos)

# 1. instala o plugin (uma linha; ativa o manifesto de Bundle automaticamente)
dsh plugin --profile web add dsh-guard-messenger

# 2. corre o DSH — o acesso local abre direto (sem login)
dsh web

# 3. (opcional) liga o bot: dsh-guard-setup + /parear (docs/ONBOARDING-TELEGRAM.md
#    para o Telegram, docs/ONBOARDING-DISCORD.md para o Discord); depois, no bot,
#    /ligar envia o link com a chave ?key=

# 4. confirma que o acesso local abre (o DSH responde direto); a borda sem chave
#    dá 401 — ver docs/INSTALL.md Passo 3
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3080/

# 5. para desligar/desinstalar
dsh plugin remove dsh-guard-messenger

examples/minimal é um exemplo mínimo instalável com o critério de aceite documentado (local abre direto / borda sem ?key= dá 401 / 200 com o link ou sessão / nenhum processo no fim).

Quando NÃO usar isto

  • Time / multiusuário. É um plugin de dono único: uma allowlist de userKey/chatKey do Telegram ou Discord (ids numéricos, nunca usernames) e a chave do link. Não há RBAC nem auditoria multi-tenant.
  • Produção / uptime. O quick tunnel é, nas palavras da própria Cloudflare, "intended for testing and development only" e "We don't guarantee any SLA or uptime".
  • Quem precisa de compliance. O TLS termina na borda da Cloudflare; o texto claro passa por lá. Não é E2E.
  • Quem quer "seguro por padrão sem pensar". Isto não existe aqui. Estás a expor um agente com shell; o plugin reduz superfície e entrega o kill switch, não elimina a categoria.
  • Máquina corporativa. trycloudflare.com tem reputação de malware documentada e muitas redes/EDRs bloqueiam ou sinalizam cloudflared.

Alternativas (comparação honesta)

Nenhuma alternativa é apresentada como ruim; várias são melhores em vários eixos. O valor está na combinação, não em vencer item a item.

Alternativa O que faz melhor O que custa Quando escolher em vez deste plugin
Tailscale (+ Funnel) Rede privada real (WireGuard), identidade por dispositivo Instalar cliente no celular e conta Quase sempre, se aceitas instalar o cliente
ngrok Ergonomia imediata; auth na borda no plano free (o quick tunnel não tem) Limites free; URL muda Quando queres auth de borda hoje
SSH + tmux Sem superfície HTTP nova, E2E de verdade Não é a Web UI; código no celular é castigo Quando só precisas de ver log e matar processo
code-server / VS Code tunnels Editor completo no browser Não é o DSH; roda em paralelo Quando o objetivo é editar ficheiro, não conduzir o agente
dsh-webui-auth Autenticação da WebUI no transporte Só autentica; sem túnel, sem bot, sem liga/desliga Se só queres autenticação, usa ele
Named tunnel + Cloudflare Access Auth antes de chegar à tua máquina Exige domínio com DNS na Cloudflare Sempre que tiveres domínio (caminho superior)
Nada (loopback puro) Risco zero de exposição Não usas do celular Sempre que não precisares mesmo

Em suma: isto não é "mais um túnel" — é o fluxo completo de um dono só (onboarding → chave no link → túnel efémero → link no celular → botão de desligar), com o bind travado em loopback e o acesso local aberto, sem login.

Compatibilidade

Faixa suportada do upstream @deepseek-ai/dsh: 0.1.0-rc.7 .. 0.1.1-rc.1 (política N/N-1). A tabela completa — versão do plugin × faixa de rc × status — está em docs/COMPATIBILITY.md, que é gerado de dsh-compat.yml (nunca editado à mão).

Atenção ao registry: a tag latest dos subpacotes @deepseek-ai/dsh-* aponta para a publicação mais antiga, não para a mais recente. Fixa a versão explicitamente.

Validação end-to-end (o que a suíte prova de verdade)

Matriz de alto nível a partir das suítes reais do repo — test/e2e/**, test/security/**, test/integration/** — referidas por ficheiro. São verificações que o CI corre, não promessas; o detalhe de cada nível e como correr está em docs/TESTING.md.

Check Expected Onde está verificado
Acesso local (127.0.0.1) a /, /api/state, upgrade abre direto, 200/101, sem desafio (o DSH não é guardado no loopback) test/integration/http/barreira.test.ts (onda 1)
Pedido pela URL do túnel sem sessão e sem ?key= 401 TEXTO PURO, sem desafio (sem popup de login) test/e2e/tunnel-cycle.test.ts, test/security/desafio-401.test.ts
?key=<token> válido pela URL do túnel 302 para a URL limpa (+ Set-Cookie de sessão); o token não aparece nos logs test/unit/http/gate.test.ts, test/unit/tunnel/proxy.test.ts
?key= inválido pela URL do túnel 401 sem desafio, sem 302 test/unit/http/gate.test.ts, test/security/desafio-401.test.ts
Com sessão válida (cookie) pela URL do túnel 200, resposta vem do despacho original test/e2e/tunnel-cycle.test.ts
Handshake de WebSocket de origem estranha (ex.: https://evil.com) recusado — allowlist exata de Origin (CWE-1385) test/security/websocket-origin.test.ts
Host que não é o publicado / DNS rebinding 403 no perímetro, byte-a-byte test/security/host-header.test.ts, test/e2e/tunnel-cycle.test.ts
Rota contornada (/..//api, %2e, barras duplicadas, /__guard/API/login) uniforme 401/403/404, nunca pass-through test/security/path-bypass.test.ts (ADV-001..020)
100.ª falha de força bruta modo restrito acende, persiste após reinício, chave/sessão pelo túnel negadas e o loopback passa test/security/nist-ceiling.test.ts
Segredo/chave a vazar (logs, respostas, frames IPC, payloads do Telegram, env, state) canário por valor falha se vazar test/security/secret-leak-canary.test.ts (ADV-050..059)
A comparação da chave vaza tempo? prova estatística de tempo constante test/security/timing-constante.test.ts
Ciclo completo do túnel com processos reais start → READY → 401 pela URL → 200 com sessão → stop, sem processo órfão test/e2e/tunnel-cycle.test.ts (T6.1)

Honestidade sobre o que esta matriz não é: o e2e usa um fake-cloudflared e uma borda falsa — nenhum byte sai de 127.0.0.1 no CI, por construção. A re-confirmação através da borda e da rede reais é a suíte test/live/** (DSH_GUARD_LIVE_TESTS=1), opt-in e fora do gate — ver docs/TESTING.md §5.

Desinstalar e reverter

dsh plugin remove dsh-guard-messenger

Deixa zero processos remanescentes e a Web UI volta ao comportamento original. Para apagar também a chave do link, o pareamento e o estado local: remove ~/.dsh/guarded-bot. Detalhe em docs/INSTALL.md.

Docs

  • docs/INSTALL.md — instalação passo a passo
  • docs/ONBOARDING-TELEGRAM.md — conectar o bot do Telegram e parear
  • docs/ONBOARDING-DISCORD.md — criar o bot do Discord, convidar e parear
  • docs/AGENTS.md — o dispatcher de agentes: config, comandos, segurança e limites
  • docs/EXPOSURE.md — o que muda quando o túnel sobe
  • docs/TUNNEL.md — quick vs named, TTL, modelo de ameaça do transporte
  • docs/THREAT-MODEL.md — atacante, mitigação, o que não mitiga
  • docs/TROUBLESHOOTING.md — sintoma → causa → o que fazer
  • docs/ARCHITECTURE.md — costuras Cordis e mapa de módulos
  • docs/TESTING.md — como rodar cada nível de teste

Contribuir e reportar

  • Encontraste uma vulnerabilidade? Não abras issue pública. Lê SECURITY.md e usa o canal privado lá descrito (Private Vulnerability Reporting ou e-mail).
  • Queres contribuir? CONTRIBUTING.md tem o ambiente em quatro comandos, os níveis de teste, o que nunca é aceite num PR e como adicionar um provedor de mensageria.
  • Código de conduta: CODE_OF_CONDUCT.md.

Licença MIT — LICENSE. O DeepSeek Harness a montante também é MIT.

—/ 5

No ratings yet

Verified DSH bundle

Commit 43781683932d

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout