Binário setuid root do Selynt Panel — gerenciador de processos para aplicações web em servidores DirectAdmin.
Warning
Este projeto está em desenvolvimento ativo. Algumas funcionalidades podem apresentar instabilidades ou mudanças de comportamento entre versões. Não é recomendado para ambientes de produção sem validação prévia.
core-selynt é invocado pelo plugin do Selynt Panel. Cada execução:
- Exige
euid=0(setuid obrigatório) - Decide sobre quem pode agir a partir do uid real de quem chamou, preservado pelo setuid. Root e o usuário web do painel podem nomear qualquer conta em
USERNAME; as demais contas agem apenas como si mesmas, e umUSERNAMEdiferente do próprio é recusado - Cria/valida o diretório de estado em
/var/lib/selynt_panel/{user}/ - Faz drop de privilégio (
setuid/setgid/initgroups+PR_SET_NO_NEW_PRIVS) antes de executar qualquer lógica - Retorna JSON em stdout e sai com código
0(sucesso),1(erro de usuário) ou2(erro de sistema)
Important
O binário é setuid root e executável por qualquer conta local do servidor.
Nada vindo do chamador é tratado como autorização: USERNAME,
SELYNT_STATE_DIR, NVM_DIR, HOME e caminhos de aplicação são validados
contra o uid real antes do uso.
Important
O binário deve ser compilado com o target x86_64-unknown-linux-musl para gerar um executável estático, sem dependência de glibc do host. Compilar sem esse target produz um binário incompatível com servidores DirectAdmin que utilizem versões diferentes de glibc.
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-muslO binário gerado estará em target/x86_64-unknown-linux-musl/release/core-selynt.
O perfil release já está configurado no Cargo.toml com strip, lto, opt-level = "z", codegen-units = 1 e panic = "abort".
O binário deve ser instalado no diretório do plugin com o bit setuid:
install -o root -g root -m 4755 target/x86_64-unknown-linux-musl/release/core-selynt \
/usr/local/directadmin/plugins/selynt_panel/bin/core-selyntVerificar:
-rwsr-xr-x 1 root root ... core-selynt
core-selynt [--debug] <COMANDO>
A flag --debug inclui um campo _debug no JSON de saída com user, home e state_dir.
Para debug em stderr durante desenvolvimento, definir SELYNT_DEBUG=1.
list Lista apps registrados do usuário
status <name> Status (RUNNING/STOPPED) e PID
start <name> Inicia o app
stop <name> [--timeout N] Para o app (padrão: 10s)
restart <name> Para e reinicia
add <name> ... Registra novo app
remove <name> [--delete-dir] Remove o app (e opcionalmente o cwd)
logs <name> [--lines N] Últimas N linhas de log stdout (padrão: 100)
[--stderr] Ler stderr em vez de stdout
domains [--domain D] Lista domínios/subdomínios do usuário
set-node-version <name> <path> Troca o runtime do app
set-memory-max <name> <valor> Define o teto de memória do app
set-isolated --isolated <bool> Liga/desliga o isolamento entre apps da conta
status-isolated Estado atual do isolamento da conta
set-locale <code> Idioma do painel para a conta
--type <tipo> Tipo do app (obrigatório; ver tipos suportados)
--entry <arquivo> Nome do arquivo de entrada (sem path, relativo ao cwd)
--host <valor> Identificador de host (usado como nome do socket Unix)
--cwd <diretório> Diretório raiz do app (padrão: apps/nodejs/{host})
--domain <domínio> Domínio associado (opcional)
--subdomain <subdomínio> Subdomínio associado (opcional)
--node-version <path> Caminho para o binário runtime (opcional). Omitido, resolve
um caminho absoluto entre os que a detecção conhece — nunca
o nome puro: o `PATH` do CGI não traz `/usr/local/bin`, e
confiar nele fazia o app subir pelo shell e falhar pelo painel
--env KEY=VAL Variável de ambiente (repetível)
admin version Versão do binário
admin list Apps de todos os usuários
admin detect-nodes Detecta runtimes instalados no sistema
admin save-node-versions <idx...> Salva versões selecionadas por índice
admin save-default-isolated Isolamento padrão para contas novas
admin set-locale <code> Idioma padrão do painel no servidor
admin diagnose Roda o diagnóstico da pilha de proxy
| Variável | Obrigatória | Descrição |
|---|---|---|
USERNAME |
Depende | Conta para a qual o comando é executado. Obrigatória quando o chamador é root. Contas comuns só podem informar o próprio nome — qualquer outro é recusado |
SELYNT_STATE_DIR |
Não | Sobrescreve o state dir. Honrada apenas para root e o usuário web do painel; conferir só o prefixo não bastava, porque /var/lib/selynt_panel/<outra-conta> também o satisfaz |
SELYNT_WEB_USER |
Não | Usuário web para ACL (alternativa ao arquivo etc/ols_web_user) |
SELYNT_DEBUG |
Não | 1 para logs de debug em stderr |
NVM_DIR |
Não | Usado por admin detect-nodes. Aceita apenas caminhos sob /opt/ ou /usr/local/: a detecção executa cada candidato para ler a versão, e sob /home/ isso seria execução de código escolhido pela conta |
--type |
Entrada | Comportamento |
|---|---|---|
node |
arquivo .js |
Interpretado: o runtime é resolvido por caminho absoluto, e o add cria o arquivo de entrada se ele não existir |
binary |
executável | Executado direto. O core não olha como o arquivo foi produzido — qualquer linguagem que gere um executável serve |
Note
O tipo binary chamava-se rust. O identificador antigo não é aceito: o core
nunca olhou a linguagem, então o nome prometia uma restrição que não existe.
Note
Cada tipo de app possui tratamento específico no start e no add. Variáveis de ambiente são sempre lidas do arquivo .env no cwd do app no momento do start, independente do tipo.
Apps devem escutar em Unix socket. O caminho do socket é passado via SELYNT_HOST e SELYNT_SOCKET.
Aplicações que abrirem portas TCP ou UDP são encerradas imediatamente (SIGTERM + SIGKILL) e o erro network_port_forbidden é retornado.
/var/lib/selynt_panel/{user}/
├── .run/
│ ├── {name}.app # Metadados (type, cwd, entry, host, ...)
│ ├── {name}.pid # PID atual
│ └── {name}.meta # uid + starttime + started_at (anti PID-reuse)
├── .sockets/
│ └── {host} # Unix socket do app
└── .proxy/
└── {host} # Marker de readiness para o proxy reverso
{cwd}/
├── .env # Variáveis de ambiente do app
└── logs/
├── {name}.out.log
└── {name}.err.log
- Drop de privilégio imediato:
initgroups+setgid+setuid+prctl(PR_SET_NO_NEW_PRIVS)antes de qualquer lógica de negócio - Setsid em processos filhos: cada app é spawnado com
setsid()viapre_exec, tornando-o líder de sessão e evitando que sinais vazem para o processo pai - Anti PID-reuse: o PID é validado contra o
starttimede/proc/{pid}/statantes de enviar sinais - Validação de UID:
statussó reporta RUNNING se o PID pertencer ao usuário atual (/proc/{pid}/status) - Autoridade pelo uid real: quem pode agir sobre quem não vem de
USERNAME, e sim do uid preservado pelo setuid. Contas comuns agem apenas como si mesmas, e os comandosadminexigem root ou o usuário web do painel .apppertence ao root: é o arquivo que diz o que executar. Se a conta pudesse escrevê-lo, escolheria o que roda com privilégio; toda escrita passa porwrite_as_root, e metadado de outro dono é recusado- Runtimes verificados antes de executar: a detecção executa cada candidato para ler a versão, então só aceita binário do root em diretório não-gravável — árvores de runtime costumam ficar com o dono de quem as descompactou
- Isolamento entre apps da mesma conta: com o isolamento ligado, cada app roda sem enxergar arquivos, processos ou sockets dos vizinhos
- Bloqueio de portas de rede: TCP/UDP são verificados via
/proc/net/{tcp,tcp6,udp,udp6}após o start, e uma varredura periódica derruba app que exponha porta alcançável de fora — bind em loopback continua permitido - ACL no socket:
setfaclcom fallback parachmodpara o usuário web - Validação de path traversal: nomes de apps e
entry/hostsão validados contra..,/e bytes nulos - Criação atômica: arquivos
.appusamcreate_newpara evitar race condition TOCTOU - Log rotation: arquivos maiores que 50 MB são truncados mantendo as últimas 5.000 linhas
Leia o guia de contribuição antes de abrir uma issue ou pull request — este binário é setuid root, e o guia explica o modelo de privilégio que toda mudança precisa respeitar. Valem também o código de conduta e a política de uso de IA.
Falha de segurança não vai em issue pública: use o relato privado, como descrito no SECURITY.md.
Copyright © 2026 NullSablex. Licenciado sob a GNU AGPL-3.0-or-later.
