Skip to content

Selynt Panel

Core Selynt

Binário setuid root do Selynt Panel — gerenciador de processos para aplicações web em servidores DirectAdmin.

Version OpenSSF Scorecard License Rust Linux DirectAdmin CI

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.


Visão geral

core-selynt é invocado pelo plugin do Selynt Panel. Cada execução:

  1. Exige euid=0 (setuid obrigatório)
  2. 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 um USERNAME diferente do próprio é recusado
  3. Cria/valida o diretório de estado em /var/lib/selynt_panel/{user}/
  4. Faz drop de privilégio (setuid/setgid/initgroups + PR_SET_NO_NEW_PRIVS) antes de executar qualquer lógica
  5. Retorna JSON em stdout e sai com código 0 (sucesso), 1 (erro de usuário) ou 2 (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.


Build

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-musl

O 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".


Instalação

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-selynt

Verificar:

-rwsr-xr-x 1 root root ... core-selynt

Uso

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.


Comandos

Gerenciamento de apps

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

Opções de add

--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)

Comandos admin (requer root ou o usuário web do painel)

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áveis de ambiente

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

Comportamento por tipo de app

--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.


Comunicação via socket

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.


Estrutura de estado

/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

Segurança

  • 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() via pre_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 starttime de /proc/{pid}/stat antes de enviar sinais
  • Validação de UID: status só 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 comandos admin exigem root ou o usuário web do painel
  • .app pertence 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 por write_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: setfacl com fallback para chmod para o usuário web
  • Validação de path traversal: nomes de apps e entry/host são validados contra .., / e bytes nulos
  • Criação atômica: arquivos .app usam create_new para evitar race condition TOCTOU
  • Log rotation: arquivos maiores que 50 MB são truncados mantendo as últimas 5.000 linhas

Contribuindo

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.

Licença

Copyright © 2026 NullSablex. Licenciado sob a GNU AGPL-3.0-or-later.

About

Binário setuid root, escrito em Rust, que executa as operações privilegiadas do Selynt Panel em servidores DirectAdmin. Faz a mediação entre o painel e o sistema — ciclo de vida dos processos, isolamento, limites de recursos e integração com o servidor web — abandonando privilégio antes de qualquer lógica e respondendo em JSON.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages