Passer au contenu principal
F

deepseek-harness-mobile

frederico-kluser/deepseek-harness-mobile

Plugin Cordis para o DeepSeek Harness v0.1: interceta a camada HTTP (auth barrier sobre o fallback da Web UI) e orquestra subprocessos de longa duracao com ciclo de vida reversivel.

Installer

dsh plugin --profile web add github:frederico-kluser/deepseek-harness-mobile

README

dsh-guarded-bot-orchestrator

CI npm version npm downloads OpenSSF Scorecard

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, a senha é gerada pela máquina, e ligas e desligas o acesso pelo Telegram.

Demo

Instalação (uma linha)

dsh plugin --profile web add dsh-guarded-bot-orchestrator

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 credencial, 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, autenticamos 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.

PropriedadeComo
O bind nunca é alargadoassertSecureBind recusa 0.0.0.0/:: no carregamento, com falha ruidosa (src/config/bind.ts)
Toda a superfície HTTP exige credencial/api, o fallback da SPA e o handshake de WebSocket passam pelo mesmo portão (src/http/gate.ts)
Origem e Host vêm primeirotrustedRemotes (403 sem credencial) e Host byte-a-byte contra DNS rebinding, antes da credencial — ordem é contrato (src/http/gate.ts:15, src/http/host-header.ts)
Senha gerada, nunca guardada em claroCSPRNG 256 bits, apresentada uma única vez (texto + QR); em disco fica só o digest SHA-256, ficheiro 0600 (src/secret/*)
Senha nunca passa por canal remotoentrega local (terminal/QR) ou token de uso único impresso no stdout do arranque — nem Telegram, nem túnel (invariante SEC-14)
Força bruta tem tetoa 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/**)
Comparação de segredo em tempo constantedigest em tempo constante, com prova estatística na suíte (src/http/auth-basic.ts, test/security/timing-constante.test.ts)
danger-full-access vetadoelevação proibida recusada como defesa em profundidade (src/permissions/deny.ts)
Só o dono pareado comanda o botallowlist de from.id do Telegram (worker/auth/allowlist.ts)
O que NÃO se garanteo TLS termina na borda da Cloudflare (texto claro passa por lá); a URL do túnel não é segredo; prompt injection continua aceite — decisões de desenho em docs/THREAT-MODEL.md §4

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. O que muda é que passa a existir um processo filho supervisionado (cloudflared) que leva o tráfego da borda da Cloudflare até esse loopback. 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 a barreira de autenticação continua no processo, no mesmo lugar, a proteger /api, o fallback da SPA e o handshake de WebSocket.

O que não muda: a superfície de ataque lógica cresce. Antes, um atacante precisava de acesso à máquina; agora precisa da senha. Trocámos "inalcançável" por "alcançável e autenticado". Essa troca é reversível a qualquer momento pelo botão de desligar.

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. Guarda o plano de controlo HTTP. Exige credencial em /api, no fallback da SPA e no 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. Gera a senha pela máquina (CSPRNG, 256 bits) e entrega-a uma única vez no terminal (texto + QR). Em disco fica só o digest. A senha nunca passa por canal remoto.
  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 portão atrás).
  4. Ligar/desligar pelo Telegram ou painel — o botão de matar na mão.

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

Como flui um pedido (arquitetura em 8 linhas)

Telemóvel (navegador)
   │  abre https://<subdomínio>.trycloudflare.com/?key=<senha>   (lê o QR do terminal)
   ▼
❨ 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 (portão)
   │  exige credencial em /api, no fallback da SPA e no handshake de WebSocket
   │  403 origem/ Host fora da allowlist · 401 sem credencial · 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: a senha é gerada pela máquina (CSPRNG, 256 bits) e entregue uma única vez em texto + QR; em disco fica só o digest. 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-guarded-bot-orchestrator

# 2. corre o DSH
dsh web

# 3. no terminal: guarda a senha que apareceu UMA vez
#    (se quiseres, segue docs/ONBOARDING-TELEGRAM.md para ligar o bot)

# 4. confirma que o portão está ativo (tem de dar 401)
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:3080/api/commands/execute

# 5. para desligar/desinstalar
dsh plugin remove dsh-guarded-bot-orchestrator

examples/minimal é um exemplo mínimo instalável com o critério de aceite documentado (401 sem credencial / 200 com credencial / nenhum processo no fim).

Quando NÃO usar isto

  • Time / multiusuário. É um plugin de dono único: uma allowlist de from.id do Telegram e uma credencial. 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.

AlternativaO que faz melhorO que custaQuando escolher em vez deste plugin
Tailscale (+ Funnel)Rede privada real (WireGuard), identidade por dispositivoInstalar cliente no celular e contaQuase sempre, se aceitas instalar o cliente
ngrokErgonomia imediata; auth na borda no plano free (o quick tunnel não tem)Limites free; URL mudaQuando queres auth de borda hoje
SSH + tmuxSem superfície HTTP nova, E2E de verdadeNão é a Web UI; código no celular é castigoQuando só precisas de ver log e matar processo
code-server / VS Code tunnelsEditor completo no browserNão é o DSH; roda em paraleloQuando o objetivo é editar ficheiro, não conduzir o agente
dsh-webui-authAutenticação da WebUI no transporteSó autentica; sem túnel, sem bot, sem liga/desligaSe só queres autenticação, usa ele
Named tunnel + Cloudflare AccessAuth antes de chegar à tua máquinaExige domínio com DNS na CloudflareSempre que tiveres domínio (caminho superior)
Nada (loopback puro)Risco zero de exposiçãoNão usas do celularSempre que não precisares mesmo

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

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.

CheckExpectedOnde está verificado
curl sem credencial a /api/commands/execute401 (o portão dispara; o despacho original não é alcançado)test/integration/http/barreira.test.ts + a ordem origem → Host → credencial em test/security/panel-exemptions.test.ts
Com sessão válida (cookie) ou Basic Auth200, resposta vem do despacho originaltest/e2e/tunnel-cycle.test.ts
Pedido pela URL do túnel sem credencial401 com WWW-Authenticate; após stop, o mesmo pedido devolve 403 e o direto ao loopback 401test/e2e/tunnel-cycle.test.ts (discriminador 401/403)
Handshake de WebSocket de origem estranha (ex.: https://evil.com)recusado — allowlist exata de Origin (CWE-1385)test/security/websocket-origin.test.ts
Rota contornada (/..//api, %2e, barras duplicadas, /__guard/API/login)uniforme 401/403/404, nunca pass-throughtest/security/path-bypass.test.ts (ADV-001..020)
Host que não é o publicado / DNS rebinding403 no perímetro, byte-a-bytetest/security/host-header.test.ts, test/e2e/tunnel-cycle.test.ts
100.ª falha de força brutamodo restrito acende, persiste após reinício, credencial pelo túnel negada e a do loopback passatest/security/nist-ceiling.test.ts
Segredo a vazar (logs, respostas, frames IPC, payloads do Telegram, env, state)canário por valor falha se vazartest/security/secret-leak-canary.test.ts (ADV-050..059)
A comparação de segredo vaza tempo?prova estatística de tempo constantetest/security/timing-constante.test.ts
Ciclo completo do túnel com processos reaisstart → READY → 401/200 pela URL → stop → 403, sem processo órfãotest/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-guarded-bot-orchestrator

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

Docs

Contribuir e reportar

  • Encontraste uma vulnerabilidade? Não abras issue pública.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 e o que nunca é aceite num PR.
  • Código de conduta: CODE_OF_CONDUCT.md.

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

Plugins associés