Skip to content

Segurança e conformidade

O SkylerX atende ambientes dev / test / prod e traz um modelo de segurança ponta a ponta — das credenciais de conexão à renderização do resultado, do envio do SQL à exportação em lote. Esta página descreve cada barreira realmente implementada no código: o que faz, o que não faz e que evidências entrega para ops e auditoria.

1. Visão geral

A segurança é segmentada em cinco etapas do fluxo de dados, cada uma com módulo próprio:

EtapaMódulo / arquivoResponsabilidade
Credenciais em discoapps/desktop/src/main/db/connectionStore.tsSenhas / chave privada SSH criptografadas no chaveiro do SO (safeStorage)
Identificação de ambientepackages/ui/src/connEnv.tsMarcação tri-cor dev/test/prod + conexão somente leitura + whitelist de statements de leitura
Interceptação de statementspackages/ui/src/sqlLint.ts7 regras heurísticas (UPDATE/DELETE sem WHERE, DROP/TRUNCATE em prod etc.)
Apresentaçãopackages/ui/src/masking.ts + DataMaskingViewDialogMascaramento na renderização + view de mascaramento persistido
Governança / auditoriacompliance.ts / PiiScannerDialog / DataContractDialog / export-encrypt.tsConformidade GB17859 (segurança nível 2.0 da China), PII scan, data contracts, exportação criptografada

A seguir, em detalhes.

2. Criptografia de senhas (chaveiro do SO)

Local: apps/desktop/src/main/db/connectionStore.ts.

Ao criar / editar conexão, a senha não vai como texto plano no SQLite. Vai por safeStorage do Electron (macOS = Keychain, Windows = DPAPI, Linux = libsecret / kwallet):

ts
function encryptPassword(plain?: string): string | null {
  if (!plain) return null
  if (safeStorage.isEncryptionAvailable()) {
    return `enc:${safeStorage.encryptString(plain).toString('base64')}`
  }
  return `plain:${Buffer.from(plain, 'utf8').toString('base64')}`
}

O campo tem prefixo, facilitando identificar a versão:

PrefixoSignificadoQuando
enc:Ciphertext do chaveiroCaminho normal, macOS / Windows / maioria do Linux
plain:Fallback base64 (apenas dev)safeStorage.isEncryptionAvailable() retorna false (container Linux sem libsecret / kwallet)
OutroCampo antigo sem prefixoDados históricos

Importante: plain: ainda funciona, mas equivale a texto plano. No Linux, instale gnome-keyring ou kwallet e force re-edição da conexão (qualquer alteração salva re-criptografa).

Chave do SSH tunnel

A config SSH contém password / privateKey / passphrase; toda a cadeia é criptografada. No listConnections, os campos secretos são removidos para evitar carga em memória:

ts
function decryptSsh(stored, withSecrets) {
  const ssh = JSON.parse(decryptPassword(stored)) as SshConfig
  return withSecrets
    ? ssh
    : { ...ssh, password: undefined, privateKey: undefined, passphrase: undefined }
}

Apenas no getConnection (conectar / editar) eles voltam completos.

3. Marcadores dev/test/prod + proteção de produção

Local: packages/ui/src/connEnv.ts.

Config tem extra.env, enum tri-estado:

ValorLabelCor (ENV_META.color)Severidade
devDesenvolvimento#4caf50 verdePadrão
testTeste#e0a020 laranjaPadrão
prodProdução#e04050 vermelhoRegras extras de SQL + confirmação dupla na execução

Conexão inteira somente leitura (extra.readOnly)

Sinalizada por connReadOnly(). Duas portas em paralelo:

  1. Nível conexão: isReadOnlyStatement(sql) aceita apenas palavras-chave iniciais select / with / show / explain / desc(ribe) / pragma; statements de escrita são bloqueados.
  2. Commit mode: somente leitura força auto (transação manual é inútil); initialCommitMode().

Marca d'água de produção

Settings → Marca d'água de produção permite customizar texto / ângulo / opacidade / cor. Em conexões prod, todas as views (editor SQL, resultados, preview de exportação) ganham marca d'água SVG, dificultando screenshots.

4. SQL Linter — 7 regras

Local: packages/ui/src/sqlLint.ts.

Scan heurístico de strings (sem parser completo), pega apenas padrões "óbvios". Três níveis:

SeveridadeFeedback UIExecuta?
errorConfirmação modalApenas após confirmar
warnToastExecuta (apenas avisa)
infoDecisão do chamador (pode marcar na margem do editor)Executa

Regras completas:

IDSeveridadeCondiçãoMensagem
no-where-updateerrorUPDATE sem WHEREUPDATE sem WHERE, atualizará toda a tabela
no-where-deleteerrorDELETE FROM sem WHEREDELETE sem WHERE, esvaziará a tabela
prod-droperrorenv=prod + DROP TABLE/DATABASE/SCHEMA/INDEX/VIEWDROP em produção
prod-truncatewarnenv=prod + TRUNCATETRUNCATE em produção
cross-joinwarnSELECT + FROM a, b (JOIN com vírgula) ou JOIN sem ON/USINGMulti-tabelas sem condição de join (provável cartesiano)
select-starinfoSELECT *Sugira listar colunas explicitamente
forgotten-limitinfoSELECT sem LIMIT / FETCH FIRST / TOP n / COUNT()SELECT sem LIMIT, pode trazer muitos dados

Restrição "barata"

Comentários são removidos com 2 regex simples (/\/\*[\s\S]*?\*\//g e /--[^\n]*/g) para que -- WHERE 1=1 não engane o linter. Tudo O(n), sem impacto perceptível.

Agregação em multi-statements

lintStatements(stmts, ctx) deduplica findings do mesmo id mantendo a severidade mais alta, ideal para "selecione um arquivo SQL inteiro e execute".

5. Data contracts (notNull / range / regex)

Local: packages/ui/src/components/DataContractDialog.vue.

Contratos antecipam "valores que NUNCA deveriam estar no campo de negócio". Quatro partes:

CampoTipoDescrição
namestringNome dado pelo usuário
tablestringschema.table aplicável
notNullstring[]Colunas não-nulas
rangeRecord<string, [min, max]>Range numérico, null = sem limite
regexRecord<string, string>Regex obrigatória
enabledbooleanOn/off

Armazenamento: localStorage.skylerx.dataContracts, JSON array.

Uso típico

json
{
  "name": "Integridade da tabela de usuários",
  "table": "public.users",
  "notNull": ["phone", "email"],
  "range": { "age": [0, 150] },
  "regex": { "email": "^[^@]+@[^@]+$", "phone": "^1\\d{10}$" },
  "enabled": true
}

Importação / exportação

  • 📋 Exportar → JSON no clipboard, fácil de compartilhar via doc / git
  • 📥 Importar → cole JSON, sobrescreve

DBA escreve contratos e distribui aos devs; ao colar no SkylerX local, os contratos passam a valer.

6. PII Scanner

Local: packages/ui/src/components/PiiScannerDialog.vue.

Heurística em duas fases: match por nome → validação por amostra.

Fase 1 — match por nome

Regex columnPattern de DEFAULT_MASK_RULES (ver próxima seção). user_phone casa (phone|mobile|tel|手机|电话) → kind phone.

Fase 2 — amostra (opcional)

Puxa as primeiras N linhas (default 50, ajustável 10-1000) e revalida com regex:

kindRegex de validação
phone/^\+?[\d\s\-()]{7,20}$/
email/^[^\s@]+@[^\s@]+\.[^\s@]+$/
idCard/^\d{15}$|^\d{17}[\dxX]$/
bankCard/^\d{12,19}$/
name / address / defaultsem; apenas nome

Hit rate < 30% → assume "coincidência de nome, não é PII" e remove.

Relatório e próximos passos

Agrupado por "hit count desc" por tabela; 📋 Exportar CSV (colunas: schema/table/column/data_type/rule/kind/sample). O CSV vai direto para auditoria; também serve para gerar a view de mascaramento (clique direito no banco → "Gerar view de mascaramento").

7. View de mascaramento (DataMaskingViewDialog)

Local: packages/ui/src/masking.ts + packages/ui/src/components/DataMaskingViewDialog.vue.

7.1 Regras embutidas

DEFAULT_MASK_RULES é a baseline; editável em Settings → Mascaramento.

RegracolumnPatternkindDefaultAlgoritmo
Telefone(phone|mobile|tel|手机|电话)phone3 primeiros + **** + 4 últimos
Email(email|mail|邮箱)email1ª letra + ***@domain
ID(id_?card|身份证|idno)idCard6 primeiros + *… + 4 últimos
Cartão(bank_?card|card_?no|账号|账户)bankCard4 primeiros **** **** 4 últimos
Nome(real_?name|user_?name|full_?name|姓名)name1ª letra + *
Endereço(address|addr|地址)address6 primeiros + ***
Senha / Token(password|passwd|secret|pwd|token|api_?key|密码)default2 primeiros + **** + 2 últimos

7.2 Mascaramento na renderização vs view persistido

Dois caminhos independentes:

  • Render-time: Settings → Mascaramento → ativar. Front aplica regra por coluna → algoritmo, sem alterar o DB; no diálogo de exportação você escolhe "original / mascarado".
  • View persistido (DataMaskingViewDialog): gera CREATE OR REPLACE VIEW ... AS SELECT mask_expr(c) ... no DB; aplicações leem a view, não a tabela original. Seis estratégias:
EstratégiaExpressão (MySQL)
raw original`c` AS `c`
md5md5(CAST(`c` AS char(4000))) AS `c`
partial N e MCONCAT(LEFT(`c`,N), '***', RIGHT(`c`,M)) AS `c`
fixed substituir'***' AS \c``
truncate truncarLEFT(`c`, max) AS `c`
nullNULL AS `c`

Ao abrir, recommendStrategy(colName) sugere; usuário pode sobrescrever. SQL editável antes de ▶ Criar view.

8. Conformidade GB17859 (segurança nível 2.0 da China)

Local: packages/ui/src/compliance.ts + packages/ui/src/components/ComplianceDialog.vue.

Cobre o que pode ser verificado direto pela conexão (não firewall / disco). Quatro estados:

SeveridadeSignificado
passConforme
warn ⚠️Não conforme mas risco controlado (auditoria off, SSL off)
failViolação séria (root remoto, usuário sem senha)
unknownNão pôde julgar (sem permissão, recurso enterprise)

Família MySQL (MySQL / MariaDB / OceanBase / TiDB) — 7 itens

IDCategoriaTítuloComo verifica
mysql.auth.password-policyAutenticaçãoPolítica de senha forteSHOW VARIABLES LIKE 'validate_password%', policy ≥ MEDIUM e length ≥ 8
mysql.audit.enabledAuditoriaAudit log ativoaudit_log_* (enterprise) ou server_audit_* (MariaDB)
mysql.auth.root-remoteAcessoroot sem login remotoSELECT user, host FROM mysql.user WHERE user='root'
mysql.auth.anonymousAcessoSem usuários anônimosmysql.user WHERE user=''
mysql.transport.sslIntegridadeSSL obrigatóriorequire_secure_transport=ON
mysql.audit.slowlogAuditoriaSlow log ativoslow_query_log=ON
mysql.integrity.binlogIntegridadeBinlog ativolog_bin=ON (PITR / replicação)

Família PostgreSQL (PG / KingbaseES / openGauss / Greenplum / CockroachDB) — 6 itens

IDCategoriaTítuloComo verifica
pg.auth.password-encryptionAutenticaçãoSCRAM-SHA-256SHOW password_encryption
pg.audit.pgauditAuditoriaExtensão pgauditpg_extension WHERE extname='pgaudit'
pg.transport.sslIntegridadeSSL ativoSHOW ssl
pg.access.superuser-countAcessoSuperusuários ≤ 2SELECT rolname FROM pg_roles WHERE rolsuper
pg.audit.log-statementAuditorialog_statement configuradoSHOW log_statement ≠ none
pg.auth.empty-passwordAutenticaçãoSem usuário com senha vaziapg_authid WHERE rolpassword IS NULL AND rolcanlogin

Exportação Markdown

Exportar Markdown chama renderReport(), agrupa por categoria, adiciona "resumo: ✅ N · ⚠️ N · ❌ N · — N" + por regra: descrição / conclusão / evidence. Nome: compliance-<safeName>-<YYYY-MM-DDTHH-MM-SS>.md.

Execução paralela

"Iniciar verificação" usa Promise.all; falha de uma não afeta as outras (try/catch → unknown); driver faz queueing.

Outros dialetos

Não-MySQL / PG: item placeholder

Verificação de conformidade não disponível para este dialeto — verifique manualmente

Oracle / SQL Server / SQLite / DM virão sob demanda.

9. Criptografia nacional chinesa SM2/SM3/SM4 (planejado)

As regras de conformidade já tratam password_encryption=md5 como algoritmo fraco (ver descrição em pg.auth.password-encryption). As APIs auxiliares de SM2 / SM3 / SM4 (para assinar / criptografar em camada de aplicação antes de gravar) ainda não foram lançadas; previstas para v0.6 em cryptoCn.ts:

  • SM2 ECC sign / encrypt / decrypt (sm-crypto)
  • SM3 message digest
  • SM4 cifra de bloco simétrica (CBC / ECB)

Assinaturas serão documentadas aqui em "Criptografia nacional chinesa SM2/SM3/SM4 API auxiliar".

10. Exportação criptografada .skbk

Local: packages/ui/src/export-encrypt.ts.

Texto qualquer (geralmente SQL dump ou config de conexão) criptografado com senha em JSON em linha única, extensão .sql.enc / .skbk.

Stack

EtapaAlgoritmoParâmetros
KDFPBKDF2-HMAC-SHA-256iter = DEFAULT_ITER = 200 000 (anotado no cabeçalho)
CifraAES-GCM 256salt 16B + iv 16B, regerados
IntegridadeAES-GCM tag 128 bitssenha errada / arquivo alterado → WRONG_PASSWORD
Magicmagic: 'SKYLERX-ENC-v1'Identifica versão do algoritmo

Sobre 200 000 iterações: OWASP 2023 recomenda ≥ 600 000 (SHA-256), mas no desktop é preciso considerar máquinas antigas (Atom CPU travaria por +1s em 600k). Para conteúdos extremamente sensíveis, é possível elevar o iter ao chamar encryptText.

Formato

json
{
  "magic": "SKYLERX-ENC-v1",
  "salt": "<base64 16B>",
  "iv":   "<base64 16B>",
  "iter": 200000,
  "data": "<base64 ciphertext + tag>"
}

Ordem fixa (bom para git diff); JSON em linha única (streaming).

Códigos de erro

ErroQuandoFeedback UI
INVALID_BLOBCampos faltando / tipo errado / magic errado"Arquivo corrompido"
WRONG_PASSWORDTag GCM falha (senha errada / arquivo alterado)"Senha incorreta" (sem distinguir, sem side-channel)

Dependência Web Crypto

Tudo via globalThis.crypto.subtle, sem terceiros. Desktop Electron renderer + browsers modernos suportam; Node 18+ também (testes). Ambientes muito antigos: Web Crypto API unavailable: upgrade to Node 18+ or a modern browser.

11. Fronteira de privacidade da IA

A IA (Anthropic / OpenAI / DeepSeek / Codex / Grok) é core do SkylerX, mas só envia o estritamente necessário:

TipoEnvia?Notas
Texto SQL atualPré-condição do chat / autocomplete
Schema hint (banco / tabela / coluna)Apenas metadados; sem dados de linha
Texto e código de erroPara "Perguntar à IA", veja seção 4
Metadados de conexão (dialeto / nome / banco)Para a IA escolher o dialeto
Dados do result setMesmo com inline ligado, só vai schema hint
Senhas / chave SSHCipher no chaveiro nunca é lido para prompts
Config de conexão completaSó dialect / database da conexão selecionada

Para isolar a IA totalmente:

  1. Settings → AI Provider → Limpar API Key → desabilita autocomplete / chat / Perguntar à IA
  2. Ou use endpoint local (Ollama / vLLM / privado), aponte endpoint para http://localhost:...

Webhooks de notificação seguem o mesmo princípio: o template default envia "título + resumo + tempo do trigger", sem dados de linha. Personalize em Settings → Notificações.

12. Atalhos de segurança

AçãoEntrada
Conformidade GB17859 (segurança nível 2.0 da China)⌘K → "Conformidade · nome da conexão" / clique direito → Compliance
PII ScanClique direito banco → PII Scanner
View de mascaramentoClique direito banco / tabela → Gerar view de mascaramento
Data Contracts⌘K → "Data Contracts" / Ferramentas → Data Contracts
Exportação criptografadaResultado / Editor → Exportar → .skbk
Política globalSettings → Mascaramento / Settings → Marca d'água
Atalhos custom (anti-toque)Settings → Atalhos

13. Limitações conhecidas

Fronteiras a ter em mente:

  • Linter é heurístico: sem parser SQL completo; raramente falha (string literal com where dentro de /* ... */). Para operações de alto risco, ative confirmação dupla de prod (digitar nome da conexão).
  • Conformidade requer permissão: mysql.user precisa de SELECT; pg_authid precisa de superuser; itens sem permissão viram unknown, não considere unknown como pass.
  • Mascaramento na renderização é UI-only: dado no DB segue original. Para impedir leitura, use view de mascaramento + lockdown de permissões.
  • Arquivo criptografado não resiste a "dicionário brute-force": 200k iter PBKDF2 = ~10^7 de custo; senhas fracas podem ser quebradas offline. Use senhas fortes ou KMS / chaves públicas internas.
  • Marcador de ambiente é soft: extra.env = 'prod' depende de o usuário marcar; um deslize ("dev") desativa as proteções prod. Padronize via "exportar config → importar no time".

Apache License 2.0 · Wuhan Skyler Network Technology Co., Ltd.