Modelo de Autenticação e Segurança
Visão Geral
O qwen serve é um daemon local por padrão e uma superfície exposta em configuração incorreta. Seu modelo de segurança é em camadas para que uma configuração errada falhe de forma segura:
- Bind — um bind fora do loopback sempre carrega um bearer: o do operador, ou um efêmero de 128 bits gerado e impresso uma única vez na inicialização. A inicialização só recusa quando uma fonte de token fornecida está explicitamente vazia/com apenas espaços, ou quando um
localhostsolicitado resolve para fora do loopback sem nenhuma fonte de token resolvida (o que nunca gera). - Autenticação Bearer — o middleware
bearerAuthcom comparação SHA-256 em tempo constante protege as rotas normais da API, exceto/healthem um bind de loopback comum (require_authmove esse endpoint para trás do bearer também). A entrada de webhook de canal é uma rota pré-bearer separada autenticada porx-qwen-webhook-secret. As rotas de documento e ativos do Web Shell permanecem pré-auth em qualquer modo. - Lista de permissão do cabeçalho Host — no loopback, apenas
localhost,127.0.0.1,[::1],host.docker.internalou o endereço de loopback vinculado exato (mais porta) são aceitos; as formas sem porta correspondentes também são aceitas ao escutar na 80 ou 443. A allowlist defende contra DNS rebinding. O listener LAN do Local Control é a exceção que sempre impõe sua verificação de Host por autoridade anunciada, qualquer que seja o bind primário. - Controle de Origin — o app de runtime sempre instala
allowOriginCorssobre uma allowlist mutável (MutableOriginAllowlist): as entradas--allow-origin <pattern>a semeiam, e o Local Control adiciona o origin da LAN enquanto ativo. Origins não correspondentes recebem o envelope de negação 403. A muralha de negação incondicional (denyBrowserOriginCors) sobrevive apenas no app de bootstrap que responde antes do runtime iniciar. - Portão de mutação por rota — rotas estritas exigem autoridade do operador. Um listener primário de loopback sem token é confiado; requisições autenticadas por bearer e requisições pareadas do Local Control também se qualificam. Uma requisição primária sem token que alcança este portão sem autoridade confiável recebe o erro distinto
code: 'token_required'. Credenciais configuradas ausentes ou inválidas e credenciais do Local Control não pareadas são rejeitadas antes pelo middleware bearer com escopo de listener com401 Unauthorizedsimples. - Autenticação via device-flow — superfície OAuth separada para provedores (
POST /workspace/auth/device-flow+ GET/DELETE em/:id).
Este documento percorre cada camada e as invariantes explícitas que o caminho de inicialização impõe.
Responsabilidades
- Recusar iniciar em configurações inseguras.
- Bloquear requisições normais da API através do bearer quando configurado, sujeito à isenção do
/healthno loopback; manter a entrada de webhook de canal atrás de seu gate independente de segredo compartilhado, e manter as verificações de Host de loopback e Origin de navegador antes das rotas autenticadas e isentas. - Fornecer um portão de mutação por rota que as rotas da Wave 4 podem ativar.
- Hospedar o registro de device-flow que conduz os fluxos OAuth dos provedores, visíveis por meio de eventos SSE.
Arquitetura
Regras de recusa na inicialização
Em run-qwen-serve.ts:
// A non-loopback bind with neither --token nor QWEN_SERVER_TOKEN first
// generates an ephemeral 128-bit base64url bearer (16 random bytes, 22
// URL-safe characters; printed once at startup, rotated per process). The
// first refusal below therefore fires only when a token source was supplied
// but is explicitly empty/whitespace, or when the requested hostname resolves
// off-loopback (localhost pinned to a non-loopback address never generates).
// Generation is loopback-suppressed, so the second refusal is a loopback-only
// fail-fast: on a non-loopback bind the generated token already satisfies
// --require-auth.
if (!isLoopbackBind(opts.hostname) && !token) {
throw new Error('Refusing to bind <host>:<port> without a bearer token. ...');
}
if (opts.requireAuth && !token) {
throw new Error(
'Refusing to start with --require-auth set but no bearer token configured. ...',
);
}Configuração de allow-origin sem token é limitada a origins HTTP(S) de loopback; entradas não-HTTP(S) mantêm seu tratamento existente:
const parsed = parseAllowOriginPatterns(opts.allowOrigins);
if (parsed.allowAny && !token) {
throw new Error(
"Refusing to start with --allow-origin '*' but no bearer token configured. ...",
);
}
if (findNonLoopbackHttpOrigin(parsed) && !token) {
throw new Error(
'Refusing to start with a non-loopback HTTP(S) --allow-origin but no bearer token configured. ...',
);
}Estas recusas são falhas explícitas de inicialização (visíveis em stderr / lançadas para o embedder), nunca silenciosas. O modelo de ameaça do #3803 proíbe explicitamente deixar um daemon se ligar além do loopback sem proteção.
runQwenServe() resolve localhost uma vez, fixa o listener nesse endereço e verifica o endereço real do listener antes de publicar autoridade de loopback confiável; se o resultado estiver fora de 127.0.0.0/8 ou ::1, a inicialização sem token falha e fecha o listener. createServeApp() não possui um socket, então seu chamador continua responsável por garantir que um hostname de loopback declarado seja vinculado apenas ao loopback. Um embed não-loopback declarado mantém rotas estritas, session shell e material de pareamento do Local Control em fail closed. Também rejeita requireAuth: true sem um token não vazio na construção para que rotas não estritas não possam acidentalmente permanecer abertas sob uma configuração endurecida inválida.
Cadeia de middlewares (ordem das requisições HTTP)
mutationGate é uma fábrica de middlewares por rota (createMutationGate retorna mutate()); as rotas chamam mutate() ou mutate({strict: true}) no momento do registro. Não é um middleware global app.use(). O log de acesso e a captura de trace-id de entrada são registrados antes da muralha de origin e da verificação de credencial de mesma origem, então esses curtos-circuitos 403/401 são registrados como qualquer outra rejeição e a linha de log ainda se junta ao trace id do chamador; ambos também precedem bearerAuth, então rejeições 401 ainda são registradas. A allowlist de Host de loopback é registrada antes da rota de health pré-auth para que a defesa contra DNS rebinding a cubra. O /health pré-auth fica abaixo da muralha de origin (sondagens cross-origin correspondidas carregam cabeçalhos CORS); o log de acesso isenta GET /health e POST */heartbeat por caminho antes de anexar seu finish logger, então sondagens exatas GET /health e POST */heartbeat permanecem sem log em qualquer posição de montagem e rejeições da muralha nesses caminhos isentos também ficam sem log (HEAD /health e GET /health/ são registrados como qualquer requisição). O rate limiting normal da API executa depois de bearerAuth e antes de express.json(), para que apenas requisições autenticadas sejam contabilizadas e corpos grandes sejam rejeitados antes do parsing quando um limite é excedido. A entrada de webhook de canal faz branch antes do bearer auth e aplica sua própria verificação de segredo compartilhado, verificação de rate de tier de mutação e parser de 1 MiB.
A superfície de mesma origem consiste em dois middlewares em server/self-origin.ts, montados separadamente: a remoção de Origin apenas para loopback é instalada antes do log de acesso (apenas remove um Origin correspondente, nunca rejeita), e installRemoteSelfOriginMiddleware executa imediatamente antes de allowOriginCors em um listener primário fora do loopback com um token configurado. O remoto corresponde um Origin canônico contra o esquema direto do socket mais a autoridade Host normalizada — cabeçalhos forwarded nunca são consultados — e autentica com bearer a correspondência antes de remover Origin, então as mutações HTTP de mesma origem do Web Shell integrado não precisam de --allow-origin. Seu predicado pré-auth (web-shell-preauth.ts) isenta os pontos de entrada do shell (/, //, /assets*, /mcp-app-sandbox, navegações exatas de documento /session/:id) da verificação de credencial porque fetches de module-script do navegador carregam Origin sem Authorization. Upgrades de WebSocket e origins https de proxy TLS frontal não são cobertos: o gate de upgrade mantém sua própria política CSWSH (origin de loopback, entrada --allow-origin, ou o próprio origin do listener do Local Control), e o origin https de um proxy nunca pode corresponder ao esquema do socket simples.
bearerAuth
- Nenhum token configurado → o middleware é um no-op (padrão de desenvolvimento em loopback). Exceção: o listener LAN do Local Control tem escopo de listener e sempre exige sua credencial pareada (
CredentialStore.isOpennunca é true paralocal-control), então nunca está aberto mesmo em um daemon sem token. - Token configurado → calcula SHA-256 do token configurado uma vez na construção; em cada requisição, calcula o hash do candidato e compara com
timingSafeEqual. Sem short-circuit de comparação de strings; sem vazamento de tempo. - Parsing do esquema:
Bearercase-insensitive conforme RFC 7235 §2.1; tolerante aSP\tHTABentre esquema e credenciais conforme RFC 7230 §3.2.6 BWS; rejeita HTAB puro como separador. - Hardening CodeQL: parsing manual com
indexOfem vez de regex com\s+/.+sobrepostos (sem risco de regex polinomial).
hostAllowlist
Apenas loopback. Mantém um Set<string> indexado por porta. Hosts permitidos:
localhost:<port>,127.0.0.1:<port>,[::1]:<port>,host.docker.internal:<port>e o endereço de loopback vinculado exato com a mesma porta. A última forma cobre o intervalo completo de loopback IPv4 suportado (127.0.0.0/8) sem admitir Hosts não relacionados.- Além disso, as formas sem porta correspondentes apenas quando vinculado à porta 80 ou 443 (conforme RFC 7230 §5.4 omissão de porta padrão).
A comparação de Host é case-insensitive — o Express normaliza nomes de cabeçalho, mas não valores, então proxies Docker que capitalizam Hosts (Localhost:4170, HOST.docker.internal) receberiam 403 com uma comparação exata de string.
Binds fora do loopback ignoram o portão primário (o operador escolheu a superfície de exposição; o token bearer protege contra spoofing de Host). O listener LAN do Local Control é a exceção: sempre impõe sua verificação de Host por autoridade anunciada, qualquer que seja o bind primário.
denyBrowserOriginCors (apenas app de bootstrap)
Rejeita qualquer requisição com cabeçalho Origin. CLI/SDK nunca definem Origin; apenas navegadores o fazem. Retorna 403 { error: 'Request denied by CORS policy' } deterministicamente, em vez do 500 HTML que o callback de erro do pacote cors produziria. O app de runtime não instala mais esta muralha — ele executa allowOriginCors sobre a allowlist mutável (abaixo); o comportamento de negação sobrevive lá como o branch de origin não correspondido. A muralha permanece no app de bootstrap (run-qwen-serve.ts) que serve requisições antes do runtime iniciar.
Exceção: as XHRs de mesma origem do Web Shell em um bind de loopback são tratadas por um middleware separado (em server/self-origin.ts) que remove Origin quando coincide com um dos self-origins canônicos de loopback (127.0.0.1, localhost, [::1], host.docker.internal) ou o endereço de loopback vinculado exato. Origins sem porta com esquema correspondente são aceitos apenas para sua porta padrão (http na 80, https na 443). Em binds fora do loopback, um segundo middleware (installRemoteSelfOriginMiddleware, no mesmo arquivo) cobre as XHRs do shell: ele autentica com bearer uma requisição cujo Origin canônico é igual ao esquema direto do socket mais a autoridade Host normalizada — cabeçalhos forwarded nunca são confiados — e remove esse Origin antes da muralha, então essas requisições não precisam de uma entrada --allow-origin. Origins cross-origin e null permanecem rejeitados, e as rotas de upgrade de WebSocket mais um origin https de proxy TLS frontal ainda exigem uma (veja Cadeia de middlewares).
allowOriginCors (app de runtime, sempre instalado)
O app de runtime instala allowOriginCors(originAllowlist) incondicionalmente; a allowlist é uma MutableOriginAllowlist semeada pelas entradas --allow-origin <pattern> (possivelmente nenhuma) e estendida em runtime enquanto o Local Control está ativo (o origin da LAN é adicionado/removido com o listener):
- Valores de
Origincorrespondentes recebemAccess-Control-Allow-Origin,Access-Control-Allow-HeaderseAccess-Control-Allow-Methods; o preflightOPTIONSretorna204. - Valores de
Originnão correspondentes recebem o mesmo403 { error: 'Request denied by CORS policy' }determinístico do modo de negação. --allow-origin '*'exige um token bearer; em binds de loopback a inicialização recusa quando nenhum está configurado, enquanto em binds fora do loopback o token efêmero gerado satisfaz a guarda (a recusa é apenas no loopback).- Sem um token, valores HTTP(S) de
--allow-originsão limitados a hosts de loopback em binds de loopback; em binds fora do loopback o token gerado satisfaz a mesma guarda. Um origin de navegador fora do loopback autentica com o bearer em toda rota da API porque caso contrário poderia exercer a API completa do operador, incluindo execução de código como o usuário do daemon; as exceções pré-auth são as rotas de documento/ativo do Web Shell e o sandbox do MCP App (/,/assets*,/mcp-app-sandbox, navegações exatas/session/:id), mais a entrada de webhook de canal, que faz branch antes debearerAuthe autentica com seu própriox-qwen-webhook-secretem vez do bearer, e — apenas em binds de loopback —/health(protegido por bearer em outros lugares). - Origins de extensão de navegador explícitos mantêm seu caminho de automação local sem token. Logs de inicialização indicam que qualquer origin de navegador permitido sem token recebe autoridade completa do operador.
parseAllowOriginPatterns()valida a sintaxe dos padrões na inicialização.- A tag de capability
allow_originé anunciada apenas quando este modo está configurado.
createMutationGate
Portão opt-in por rota. Matriz de comportamento:
| Autoridade do daemon/requisição | Opções da rota | Resultado |
|---|---|---|
| token configurado | qualquer | passthrough¹ |
| listener primário trusted-loopback | qualquer | passthrough |
| listener pareado do Local Control | strict: true | passthrough |
| requisição primária sem token sem autoridade trusted-loopback | strict: true | 401 { code: 'token_required' } |
| qualquer deployment sem token | strict: false | passthrough |
¹ Qualquer configuração de token faz o bearerAuth global impor autenticação bearer antes do portão nas rotas normais da API, exceto /health no loopback a menos que --require-auth esteja definido. A entrada de webhook de canal autentica com seu próprio segredo compartilhado antes deste middleware. O portão é redundante, mas inofensivo nas rotas que protege. --require-auth não é em si autenticação e é válido apenas com um token.
O modo trusted-loopback é derivado uma vez de loopback bind && no configured token && !requireAuth. Ele autoriza apenas requisições que chegam pelo listener primário. Não marca o marcador interno de autenticação bearer, então credenciais de listener e autoridade de deployment permanecem fatos distintos. O formato code: 'token_required' permanece para daemons mais antigos e embeds não confiáveis sem token cujas requisições alcançam o portão estrito, para que clientes SDK possam exibir uma dica de configuração em vez de um 401 genérico. Falhas de credencial de token configurado e do Local Control mantêm a resposta anterior 401 Unauthorized simples.
As respostas de status e habilitação do Local Control expõem sua URL de pareamento e QR apenas para chamadores com autoridade do operador: chamadores confiáveis do listener primário, chamadores primários autenticados por bearer e clientes LAN já pareados. Chamadores LAN não pareados e embeds não confiáveis não podem recuperá-la. A habilitação ainda exige o listener primário; clientes LAN podem acessar após o pareamento ou solicitar desabilitação sob as regras existentes.
Rotas estritas da Wave 4+: /workspace/memory, /workspace/agents/*,
/workspace/agents/generate, /file/write, /file/edit,
/workspace/tools/:name/enable, /workspace/mcp/:server/restart,
/workspace/mcp/:server/{enable,disable,authenticate,clear-auth},
/workspace/mcp/servers (POST/DELETE), /workspace/auth/device-flow,
/workspace/init, /session/:id/approval-mode, /session/:id/rewind e
/session/:id/shell.
O rewind permanece apenas REST no SDK TypeScript mesmo quando um transporte ACP está configurado. Isso preserva o portão de mutação estrito e os cabeçalhos de identidade bearer/cliente; a tabela de rotas ACP intencionalmente não possui mapeamento de rewind. O roteamento de proprietário também reverifica a confiança do workspace antes que o rewind ou o shell alcancem uma ponte de runtime secundária. IDs de sessão ativa duplicados falham de forma segura como ambiguous_session_owner em vez de fazer fallback para o runtime primário.
Isenção do /health
Em binds de loopback, /health é registrado antes do middleware bearer para que sondas de liveness dentro do pod não precisem portar o token. Binds fora do loopback protegem /health com bearer como qualquer outra rota. --require-auth remove a isenção: /health exige Authorization: Bearer <token> mesmo no loopback. A entrada de webhook de canal permanece fora do bearer auth em qualquer modo e exige seu próprio x-qwen-webhook-secret.
Identidade de cliente v1 (X-Qwen-Client-Id) é auto-declarada
O daemon valida apenas o formato de X-Qwen-Client-Id
([A-Za-z0-9._:-]{1,128}) e rastreia os IDs de cliente conectados por sessão. Atualmente não realiza prova de posse. Um cliente que observar originatorClientId no SSE pode registrar novamente o mesmo ID e se passar por esse originador em requisições posteriores.
Impacto:
designated— um chamador remoto pode se passar pelo originador e votar em uma requisição destinada apenas ao originador do prompt.consensus— se o ID falsificado já estava no snapshotvotersAtIssue, ele pode votar.local-onlynão é afetado porque usafromLoopbackcomo portão, que o daemon preenche a partir do endereço remoto da conexão.first-respondernão é afetado porque é agnóstico em relação à identidade.
Um mecanismo futuro de pair-token emitirá um segredo por sessão a partir de POST /session; votos designated / consensus precisarão apresentá-lo. Até lá, deployments que precisam de uma política designated reforçada devem usar bind em loopback ou executar atrás de um proxy reverso autenticado. Veja 04-permission-mediation.md para detalhes no nível de política.
Autenticação via device-flow
Superfície OAuth separada para autenticação de provedores. O identificador de provedor v1 é qwen-oauth, mas o tier gratuito do Qwen OAuth foi descontinuado em 2026-04-15; novas configurações devem usar um provedor de autenticação atualmente suportado quando disponível.
POST /workspace/auth/device-flow— inicia um fluxo; retorna{deviceFlowId, providerId, expiresAt, verificationUrl, userCode}.GET /workspace/auth/device-flow/:id— consulta o estado.DELETE /workspace/auth/device-flow/:id— cancela.GET /workspace/auth/status— snapshot da conta / provedor atual.
Os eventos SSE auth_device_flow_{started, throttled, authorized, failed, cancelled} distribuem o estado do fluxo para todos os assinantes, mantendo interfaces multi-cliente sincronizadas. Veja 09-event-schema.md.
Implementação: packages/cli/src/serve/auth/device-flow.ts + qwen-device-flow-provider.ts.
Defesa contra injeção de log / Trojan Source: sanitizeForStderr(value) (device-flow.ts) substitui caracteres de controle ASCII e caracteres de controle Unicode por ?. Um IdP malicioso poderia forjar linhas de log ou ocultar payloads:
| Intervalo | Motivo da remoção |
|---|---|
\x00–\x1f, \x7f, \x80–\x9f | Controles ASCII C0 / DEL / C1, escapes de terminal e falsificação de linhas de log. |
| U+200B-U+200F | Caracteres de largura zero mais LRM / RLM; invisíveis, mas podem alterar a renderização do terminal. |
| U+2028-U+2029 | LINE / PARAGRAPH SEPARATOR; muitos terminais com suporte a Unicode os tratam como quebras de linha. |
| U+202A-U+202E | Controles de EMBEDDING / OVERRIDE bidirecionais. |
| U+2066-U+2069 | Controles de ISOLATE bidirecionais (LRI / RLI / FSI / PDI), o principal vetor do CVE-2021-42574 “Trojan Source” . Um IdP usando U+2066 (LRI) em vez de U+202D (LRO) pode contornar filtros apenas de EMBEDDING/OVERRIDE com reordenação visual similar. |
| U+FEFF | BOM / zero-width no-break space. |
O comprimento é preservado substituindo cada code point removido por ? em vez de deletá-lo, para que operadores ainda possam ver que algo estava presente naquele índice. Ambas as camadas usam o sanitizador: qwenDeviceFlowProvider sanitiza oauthError do IdP, e o observador de late-poll do registro sanitiza valores controlados pelo provedor interpolados em hints de auditoria (latePollResult.kind / lateErr.name).
A tag de capability auth_device_flow é anunciada incondicionalmente; as próprias rotas retornam 400 unsupported_provider se o daemon não puder atender a um provedor específico. A lista de provedores suportados está em /workspace/auth/status em vez de /capabilities para manter o formato do descritor uniforme.
Fluxo de Trabalho
Requisição bem-sucedida com autenticação bearer
Modos de falha da autenticação bearer
Todos retornam 401 { error: 'Unauthorized' } (uniforme entre missing header / wrong scheme / wrong token para que sondagens não consigam distinguir).
Sombra do --require-auth
Após autenticar, caps.features.includes('require_auth') confirma que o deployment está reforçado.
Mutação estrita no loopback confiável
Estado e Ciclo de Vida
- O token bearer é lido na inicialização e sofre trim (newlines de
cat token.txtquebrariam a comparação silenciosamente). - O modo
--open-with-auth(exclusivo da CLI) executa antes da inicialização: após verificações determinísticas de loopback/Web Shell, aplica a mesma seleção de opção sobre ambiente e preencheServeOptions.tokencom 32 bytes aleatórios codificados em base64url somente quando nenhum token selecionado não vazio existe. A credencial gerada tem tempo de vida do processo, não é escrita emprocess.envnem persistida pelo daemon, e chega ao navegador através do fragmento de URL existente. O Web Shell retém sua cópia no navegador emsessionStoragepor aba.--opensimples e chamadores diretos derunQwenServe()nunca a geram. - O Set de Hosts permitidos é cacheado por porta; reconstruído na mudança de porta (efêmera
0→ porta real apóslisten). - O portão de mutação constrói
passthroughestrictDenieruma vez por build do app; a chamada por rota retorna o closure cacheado (sem alocação por requisição). - O registro de device-flow é descartado na Fase 1 do
shutdown()para que fluxos pendentes resolvam comocancelledantes do teardown HTTP.
Dependências
node:crypto—createHash,timingSafeEqual.packages/cli/src/serve/loopback-binds.ts—isLoopbackBind.packages/cli/src/serve/auth/device-flow.ts— máquina de estado do device-flow.@qwen-code/acp-bridge— expõe eventos de device-flow no barramento SSE por sessão.
Configuração
| Origem | Parâmetro | Efeito |
|---|---|---|
| Env | QWEN_SERVER_TOKEN | Token bearer (com trim). |
| Flag | --token | Token bearer (sobrescreve o env). |
| CLI flags | --open-with-auth | Reutiliza ou gera um bearer do Web Shell no loopback antes da inicialização do daemon. |
| Flag | --require-auth | Estende bearer para loopback + /health. Inicia apenas com um token. |
| Flag | --hostname | Bind fora do loopback sempre carrega um bearer — --token, QWEN_SERVER_TOKEN, ou um efêmero gerado; uma fonte explicitamente vazia recusa. |
| Flag | --allow-origin <pattern> | Alterna para modo de lista de permissão CORS. Wildcard e origins HTTP(S) fora do loopback exigem um token. |
| Tags de capability | require_auth (condicional), auth_device_flow (sempre), allow_origin (condicional) | Veja 11-capabilities-versioning.md. |
Observações e Limitações Conhecidos
--require-authoculta o preflight de recursos. Clientes não autenticados não podem descobrir a tagrequire_auth; sua superfície de descoberta é o próprio corpo 401.- Ordenação do body-parser no portão de mutação: respostas 401 do
mutationGate({strict: true})são disparadas depois queexpress.json()faz o parsing do corpo. Pior caso em um listener saturado:--max-connections × express.json({limit: '10mb'})≈ 2,5 GB transitórios. Entrypoints de produção fora do loopback já exigem autenticação bearer antes do parser normal da API; a entrada de webhook de canal verifica seu segredo compartilhado antes de seu parser separado de 1 MiB. Embeds diretos não confiáveis são responsáveis por sua exposição de listener. - Remoção de Origin de mesma origem em
server.tsocorre antes deallowOriginCors. Se uma mudança futura mover a remoção para outro lugar, o Web Shell quebrará. - A comparação de token é sobre o digest SHA-256, não sobre o token bruto. Reduz o vazamento de tempo ao colapsar comparações de token de tamanho variável em uma comparação de digest de tamanho fixo.
- O daemon não possui mTLS, assinatura de requisições ou prova de posse via pair-token atualmente.
--rate-limitfornece rate limiting HTTP por chave de client-id / IP; não é autenticação de identidade de cliente.
Referências
packages/cli/src/serve/auth.ts(arquivo inteiro)packages/cli/src/serve/run-qwen-serve.ts(regras de recusa)packages/cli/src/serve/loopback-binds.tspackages/cli/src/serve/auth/device-flow.tspackages/cli/src/serve/auth/qwen-device-flow-provider.ts- Modelo de ameaça para usuários:
../../users/qwen-serve.md. - Referência do protocolo:
../qwen-serve-protocol.md.