Skip to content

Sécurité et conformité

SkylerX cible simultanément les environnements dev / test / prod, avec un modèle de sécurité end-to-end intégré, couvrant des identifiants de connexion au rendu des résultats, de la soumission SQL à l'export bulk. Cette page décrit chaque défense réellement implémentée : ce qu'elle fait, ce qu'elle ne fait pas, et les preuves disponibles pour ops et audit conformité.

1. Vue d'ensemble

Le modèle de sécurité est segmenté selon le "flux de données" en 5 étapes, chacune avec un module dédié :

ÉtapeModule / fichierRôle principal
Stockage identifiantsapps/desktop/src/main/db/connectionStore.tsMots de passe / clés SSH chiffrés via trousseau OS (Electron safeStorage)
Identification envpackages/ui/src/connEnv.tsMarqueurs 3 couleurs dev / test / prod + connexion read-only + whitelist d'instructions de lecture
Interception SQLpackages/ui/src/sqlLint.ts7 règles heuristiques (UPDATE/DELETE sans WHERE, DROP/TRUNCATE en prod, etc.)
Présentation donnéespackages/ui/src/masking.ts + DataMaskingViewDialogMatch par regex de nom de colonne → masquage au rendu + vue masquée en base
Gouvernance / auditcompliance.ts / PiiScannerDialog / DataContractDialog / export-encrypt.tsCheck Conformité GB17859, scanner PII, data contracts, export chiffré

Détails par étape ci-dessous, basés sur le code.

2. Chiffrement des mots de passe (trousseau OS)

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

Lors de création / modification d'une connexion, le mot de passe ne va pas en clair dans SQLite, mais passe par Electron safeStorage (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')}`
}

Le champ stocké a toujours un préfixe pour identifier la version :

PréfixeSensQuand
enc:Cipher trousseau OSChemin normal, macOS / Windows / la plupart Linux
plain:base64 (uniquement dev)safeStorage.isEncryptionAvailable() faux, ex. conteneur Linux sans libsecret / kwallet
AutresChamps legacy sans préfixeDonnées historiques

Important : plain: fonctionne toujours, mais c'est comme du clair. Sous Linux, installez gnome-keyring ou kwallet, puis l'utilisateur ré-édite une connexion (tout enregistrement déclenche un re-chiffrement).

Clés tunnel SSH

La config SSH a password / privateKey / passphrase, tout passe par la même chaîne de chiffrement. À l'affichage liste (listConnections), les clés sont retirées pour éviter qu'elles traînent en mémoire :

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

Seule la vraie connexion / le re-remplissage du formulaire (getConnection) renvoie les clés complètes.

3. Marqueur d'environnement dev / test / prod + protection production

Code : packages/ui/src/connEnv.ts

Le champ extra.env de la config connexion stocke un tri-état :

ValeurLabel UICouleur (ENV_META.color)Sévérité
devDéveloppement#4caf50 vertStandard
testTest#e0a020 orangeStandard
prodProduction#e04050 rougeRègles SQL supplémentaires + double confirmation avant exécution

Connexion read-only (extra.readOnly)

Read-only marqué par connReadOnly(). SkylerX vérifie à 2 endroits indépendamment :

  1. Niveau connexion : isReadOnlyStatement(sql) intercepte les writes via whitelist du premier mot-clé (select / with / show / explain / desc(ribe) / pragma), tout autre = interdit.
  2. Mode commit : read-only force auto commit (la transaction manuelle n'a pas de sens en lecture) ; voir initialCommitMode().

Filigrane production

Settings → Filigrane production permet de personnaliser texte / angle / transparence / couleur ; superpose un filigrane SVG sur toutes les vues des connexions prod (éditeur SQL, résultats, aperçu d'export), anti-screenshot.

4. SQL Linter — 7 règles intégrées

Code : packages/ui/src/sqlLint.ts

Scan heuristique sur chaînes, pas de parser complet, intercepte uniquement les patterns "manifestement dangereux". Résultats à 3 niveaux :

SévéritéRetour UIExécuté ?
errorPopup modale, double confirmationExécuté après confirmation
warnToastExécuté (juste informatif)
infoDécision côté caller (badge possible)Exécuté

Tableau complet des règles :

ID règleSévéritéConditionMessage
no-where-updateerrorUPDATE au début + pas de WHEREUPDATE sans WHERE, met à jour toute la table
no-where-deleteerrorDELETE FROM + pas de WHEREDELETE sans WHERE, vide toute la table
prod-droperrorenv=prod + DROP TABLE/DATABASE/SCHEMA/INDEX/VIEWExécution de DROP XXX en production
prod-truncatewarnenv=prod + TRUNCATETRUNCATE en production
cross-joinwarnSELECT + FROM a, b (virgule JOIN) ou JOIN sans ON/USINGMulti-tables sans condition de jointure (produit cartésien suspecté)
select-starinfoSELECT *SELECT *, mieux d'expliciter les colonnes
forgotten-limitinfoSELECT sans LIMIT / FETCH FIRST / TOP n / COUNT()SELECT sans LIMIT, peut ramener beaucoup

Contraintes "cheap"

Le retrait des commentaires se fait avec 2 regex simples (/\/\*[\s\S]*?\*\//g et /--[^\n]*/g) pour ne pas se faire piéger par un -- WHERE 1=1. Toutes les règles sont O(n) string scan, sans ralentir le chemin chaud.

Agrégation multi-instructions

lintStatements(stmts, ctx) regroupe par id en gardant la sévérité max, idéal pour "coller un fichier SQL et tout exécuter".

5. Data contracts (notNull / range / regex)

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

Les contrats permettent de pré-signaler les "valeurs interdites" sur des champs métier. Chaque contrat a 4 parties :

ChampTypeDescription
namestringNom du contrat
tablestringschema.table cible
notNullstring[]Colonnes non NULL
rangeRecord<string, [min, max]>Plage numérique, null = illimité
regexRecord<string, string>Regex à matcher
enabledbooleanToggle

Stockage localStorage.skylerx.dataContracts, tableau JSON.

Exemple typique

json
{
  "name": "Intégrité table users",
  "table": "public.users",
  "notNull": ["phone", "email"],
  "range": { "age": [0, 150] },
  "regex": { "email": "^[^@]+@[^@]+$", "phone": "^1\\d{10}$" },
  "enabled": true
}

Import / export

  • 📋 Exporter → JSON dans le presse-papier, à coller dans doc partagé / repo git
  • 📥 Importer → coller JSON pour écraser la liste

DBA écrit les contrats, distribués par équipe / projet, chargés localement dans SkylerX et actifs auto.

6. Scan de champs sensibles (PII Scanner)

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

Heuristique en 2 phases : match nom de colonne → validation échantillon.

Phase match nom

Via columnPattern regex de DEFAULT_MASK_RULES (voir section suivante). Ex. user_phone match (phone|mobile|tel|手机|电话), classé phone.

Phase validation échantillon (optionnel)

Pour les colonnes matchées, charge N lignes (défaut 50, ajustable 10-1000), regex de double validation :

kindRegex de validation
phone/^\+?[\d\s\-()]{7,20}$/
email/^[^\s@]+@[^\s@]+\.[^\s@]+$/
idCard/^\d{15}$|^\d{17}[\dxX]$/
bankCard/^\d{12,19}$/
name / address / defaultAucun, nom de colonne seul

Hit < 30% = "coïncidence de nom, pas vraiment PII", retiré du rapport.

Rapport et suite

Rapport groupé par table en tri desc des hits, 📋 Exporter CSV (colonnes : schema/table/column/data_type/rule/kind/sample). CSV livrable à l'audit conformité ; clic droit base → "Générer vue masquée" pour ces colonnes.

7. Vue masquée (DataMaskingViewDialog)

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

7.1 Règles de masquage intégrées

DEFAULT_MASK_RULES est la baseline, modifiable dans Settings → Masquage.

RèglecolumnPatternkindActif par défautAlgorithme
Téléphone(phone|mobile|tel|手机|电话)phone3 premiers + **** + 4 derniers
Email(email|mail|邮箱)emailPremière lettre + ***@domain
ID Card(id_?card|身份证|idno)idCard6 premiers + *… + 4 derniers
Carte bancaire(bank_?card|card_?no|账号|账户)bankCard4 premiers **** **** 4 derniers
Nom(real_?name|user_?name|full_?name|姓名)namePremier caractère + * (reste caché)
Adresse(address|addr|地址)address6 premiers caractères + ***
Mot de passe / Token(password|passwd|secret|pwd|token|api_?key|密码)default2 premiers + **** + 2 derniers

7.2 Masquage au rendu vs vue masquée en base

Deux chemins indépendants :

  • Masquage au rendu : Settings → Masquage → Activer. Front masque selon nom de colonne → règle → algorithme, sans toucher à la base ; export = choix "Original / Masqué" dans le dialogue.
  • Vue masquée en base (DataMaskingViewDialog) : génère CREATE OR REPLACE VIEW ... AS SELECT mask_expr(c) ... SQL en base, les apps lisent la vue, pas la table directe. 6 stratégies :
StratégieExpression SQL générée (exemple MySQL)
raw original`c` AS `c`
md5md5(CAST(`c` AS char(4000))) AS `c`
partial N premiers M derniersCONCAT(LEFT(`c`,N), '***', RIGHT(`c`,M)) AS `c`
fixed remplacement'***' AS \c``
truncate troncatureLEFT(`c`, max) AS `c`
nullNULL AS `c`

À l'ouverture, recommendStrategy(colName) propose une stratégie par colonne, ajustable. SQL généré modifiable avant exécution (▶ Créer la vue).

8. Vérification de Conformité GB17859 (sécurité chinoise niveau 2.0)

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

Vérifie ce qui est "vérifiable depuis une connexion DB", sans toucher au firewall / chiffrement de disque (niveau OS). 4 états :

SévéritéSens
passConforme
warn ⚠️Non conforme mais risque maîtrisé (audit off, SSL off)
failViolation grave (root remote ouvert, mot de passe vide)
unknownIndéterminé (privilèges insuffisants, feature commerciale)

Famille MySQL (MySQL / MariaDB / OceanBase / TiDB) — 7 règles

IDCatégorieTitreDétection
mysql.auth.password-policyIdentificationPolitique de mot de passe forteSHOW VARIABLES LIKE 'validate_password%', policy ≥ MEDIUM et length ≥ 8
mysql.audit.enabledAuditAudit log activéaudit_log_* (entreprise) ou server_audit_* (MariaDB)
mysql.auth.root-remoteContrôle d'accèsroot sans accès remoteSELECT user, host FROM mysql.user WHERE user='root'
mysql.auth.anonymousContrôle d'accèsPas d'utilisateur anonymemysql.user WHERE user=''
mysql.transport.sslIntégritéSSL forcérequire_secure_transport=ON
mysql.audit.slowlogAuditSlow query log activéslow_query_log=ON
mysql.integrity.binlogIntégritébinlog activélog_bin=ON (PITR / réplication)

Famille PostgreSQL (PG / KingbaseES / openGauss / Greenplum / CockroachDB) — 6 règles

IDCatégorieTitreDétection
pg.auth.password-encryptionIdentificationAlgorithme SCRAM-SHA-256SHOW password_encryption
pg.audit.pgauditAuditExtension pgaudit installéepg_extension WHERE extname='pgaudit'
pg.transport.sslIntégritéSSL activéSHOW ssl
pg.access.superuser-countContrôle d'accèsNombre de superusers contrôlé (≤ 2)SELECT rolname FROM pg_roles WHERE rolsuper
pg.audit.log-statementAuditlog_statement configuréSHOW log_statement ≠ none
pg.auth.empty-passwordIdentificationPas d'utilisateur connectable sans mot de passepg_authid WHERE rolpassword IS NULL AND rolcanlogin

Export rapport Markdown

Exporter Markdown appelle renderReport() : groupé par catégorie, avec stat "Total : ✅ N · ⚠️ N · ❌ N · — N" + description / conclusion / evidence brute par règle. Nom auto : compliance-<safeName>-<YYYY-MM-DDTHH-MM-SS>.md.

Exécution parallèle

"Démarrer la vérification" lance Promise.all en parallèle, échec d'une règle = unknown (try/catch), n'affecte pas les autres, le driver gère lui-même la file.

Autres dialectes

Hors MySQL / PG, placeholder :

Pas de check de conformité pour ce dialecte — confirmation manuelle requise

Oracle / SQL Server / SQLite / 达梦 à venir.

9. Cryptographie nationale chinoise SM2 / SM3 / SM4 (planifié)

Le jeu de règles de conformité considère déjà "password_encryption=md5 faible par norme nationale chinoise / Conformité GB17859 (sécurité chinoise niveau 2.0)" (voir pg.auth.password-encryption). Les API auxiliaires SM2 / SM3 / SM4 (chiffrement/signature applicatif avant insertion) ne sont pas encore publiées, prévu en v0.6 dans un module cryptoCn.ts :

  • SM2 signature / chiffrement courbe elliptique (basé sm-crypto)
  • SM3 hash
  • SM4 chiffrement bloc symétrique (CBC / ECB)

Une fois les signatures stables, une section "API auxiliaire SM" sera ajoutée à cette page.

10. Export chiffré .skbk

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

Chiffre un texte (typiquement dump SQL ou config connexion) avec mot de passe en JSON one-line, suffixes .sql.enc / .skbk.

Stack algorithmique

ÉtapeAlgoParamètres
Dérivation cléPBKDF2-HMAC-SHA-256iter = DEFAULT_ITER = 200 000 (ajustable, dans l'en-tête)
ChiffrementAES-GCM 256salt 16 octets + iv 16 octets, regénérés à chaque fois
IntégritéTag auth AES-GCM 128-bitMot de passe faux / fichier modifié → WRONG_PASSWORD direct
En-têtemagic: 'SKYLERX-ENC-v1'Identification version pour évolution

Compromis PBKDF2 iter 200 000 : OWASP 2023 recommande SHA-256 ≥ 600 000, mais desktop = vieilles machines à considérer (Atom CPU = 1+ s sur 600k). Pour contenus extrêmement sensibles, ajustez iter lors de encryptText.

Format de sérialisation

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

Ordre des champs fixe pour git diff ; JSON une ligne pour streaming.

Codes d'erreur

ErreurQuandRetour UI
INVALID_BLOBParsing : champ manquant / type incorrect / magic ne match pas"Format de fichier corrompu"
WRONG_PASSWORDTag GCM invalide (mot de passe faux / fichier modifié)"Mot de passe erroné" (pas de distinction des deux causes, anti side-channel)

Dépendance Web Crypto

Tout via globalThis.crypto.subtle, sans dépendance tierce. Desktop Electron renderer + navigateurs modernes OK ; Node 18+ aussi (pour tests). Environnements anciens → Web Crypto API unavailable: upgrade to Node 18+ or a modern browser.

11. Frontière de confidentialité IA

L'assistant IA (Anthropic / OpenAI / DeepSeek / Codex / Grok) est central, mais ce qu'il envoie à l'API tierce est strictement limité au contexte nécessaire :

TypeEnvoyé ?Note
SQL courantPrémisse du chat / complétion déclenché par l'utilisateur
Schema hint (noms base / table / colonne)Métadonnées seulement, aucune donnée de ligne
Message d'erreur + codePour le diagnostic "Demander à l'IA", voir doc AI §4
Métadonnées connexion (dialecte / nom / base)Aide l'IA à choisir le bon dialecte
Données de résultatsMême avec complétion IA, seulement le schema hint, jamais le contenu des lignes
Mot de passe connexion / clé privée SSHChiffré dans le trousseau, jamais lu pour prompt
Toute la config localeSeulement dialect / database de la connexion courante

Pour isolation totale de l'IA :

  1. Settings → AI Provider → vider API Key → désactive complétion / chat / Demander à l'IA
  2. Ou endpoint local (Ollama / vLLM / déployé privé), pointez endpoint vers http://localhost:...

Webhooks IA suivent les mêmes règles : le corps de notif par défaut = "titre + résumé + heure", pas de données de ligne. Modifiable dans Settings → Notifications.

12. Aide-mémoire des entrées sécurité

ActionEntrée
Check Conformité GB17859⌘K → "Conformité GB17859 (sécurité chinoise niveau 2.0) · nom connexion" / clic droit connexion → Conformité
Scan PIIClic droit base → PII Scanner
Vue masquéeClic droit base / table → Générer vue masquée
Data contracts⌘K → "Data contracts" / Outils → Data contracts
Export chiffréGrille / Éditeur SQL → Exporter → .skbk
Politiques sécurité globalesSettings → Masquage / Settings → Filigrane prod
Raccourcis (anti-misclic)Settings → Raccourcis

13. Limites connues

Au niveau code, à connaître pour DBA :

  • Le SQL Linter est heuristique : pas de parser complet, peut rater des cas rares (ex. commentaires /* ... */ imbriqués + where dans chaîne littérale). Pour opérations critiques, combinez avec double confirmation prod (saisie du nom de connexion).
  • Conformité requiert les droits de lecture appropriés : mysql.user SELECT, pg_authid superuser ; sans droits → unknown, ne pas confondre unknown avec pass.
  • Masquage rendu seulement côté UI : la base contient toujours l'original. Pour empêcher l'app de lire l'original, vue masquée + restrictions de privilèges DB.
  • Le fichier chiffré n'empêche pas le bruteforce dictionnaire : 200k iter PBKDF2 ≈ 10⁷ de coût, un mot de passe faible reste crackable offline. Utilisez un mot de passe fort, ou un KMS / distribution clé publique en équipe.
  • Le marqueur env est une contrainte molle : extra.env = 'prod' est saisi par l'utilisateur ; un slip vers dev désactive les protections prod. Standardisez en équipe via "export config → import" entre collègues.

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