Run CodeQL in a database
Une fois votre code extrait dans une base de données, vous pouvez l’analyser en utilisant des requêtes CodeQL. Les experts GitHub, les chercheurs en sécurité et les contributeurs de la communauté rédigent et maintiennent les requêtes CodeQL par défaut. Vous pouvez également écrire vos propres requêtes.
Les requêtes CodeQL peuvent être utilisées dans l’analyse de code pour détecter des problèmes dans votre code source et identifier des vulnérabilités potentielles. Vous pouvez aussi écrire des requêtes personnalisées pour détecter des problèmes spécifiques à chaque langage utilisé dans votre projet.
Il existe deux types importants de requêtes :
- Les requêtes d’alerte mettent en évidence des problèmes à des emplacements spécifiques dans votre code.
- Les requêtes de chemin décrivent le flux d’informations entre une source et une cible (sink) dans votre code.
Requête CodeQL simple
La structure de base d’une requête CodeQL utilise l’extension de fichier .ql
et contient une clause select
. Voici un exemple de structure de requête :
/**
*
* Query metadata
*
*/
import /* ... CodeQL libraries or modules ... */
/* ... Optional, define CodeQL classes and predicates ... */
from /* ... variable declarations ... /
where / ... logical formula ... /
select / ... expressions ... */
Métadonnées des requêtes
L’utilisation de CodeQL avec l’analyse de code convertit les résultats de manière à mettre en évidence les problèmes potentiels que les requêtes sont conçues pour détecter. Les requêtes contiennent des propriétés de métadonnées qui indiquent comment les résultats doivent être interprétés.
Les métadonnées de requête permettent de :
- Identifier vos requêtes personnalisées lorsque vous les ajoutez à votre dépôt GitHub.
- Fournir des informations sur l’objectif de la requête.
Les métadonnées peuvent inclure :
- une description de la requête,
- un identifiant unique,
- le type de problème détecté (alerte ou chemin).
Les métadonnées spécifient également comment interpréter et afficher les résultats de la requête.
GitHub propose un guide de style recommandé pour les métadonnées des requêtes. Vous pouvez le consulter dans la documentation CodeQL.
Voici un exemple de métadonnées pour l’une des requêtes standard en Java :

CodeQL n’interprète pas les requêtes qui ne contiennent pas de métadonnées.
Il affiche ces résultats sous forme de tableau, sans les afficher dans le code source.
Syntaxe QL
QL est un langage de requête déclaratif et orienté objet. Il est optimisé pour permettre une analyse efficace des structures de données hiérarchiques, en particulier des bases de données représentant des artefacts logiciels.
La syntaxe de QL est similaire à celle de SQL, mais sa sémantique est basée sur Datalog, un langage de programmation logique déclaratif souvent utilisé comme langage de requête. Étant principalement un langage logique, toutes les opérations en QL sont des opérations logiques. QL hérite également des prédicats récursifs de Datalog et ajoute la prise en charge des agrégats pour rendre les requêtes complexes plus concises et simples.
Le langage QL est composé de formules logiques. Il utilise des connecteurs logiques courants comme and
, or
, et not
, ainsi que des quantificateurs comme forall
et exists
. Grâce aux prédicats récursifs, vous pouvez écrire des requêtes complexes en utilisant la syntaxe QL de base et des agrégats comme count
, sum
, et average
.
Requêtes de chemin (Path queries)
La manière dont l’information circule dans un programme est essentielle. Des données apparemment inoffensives peuvent circuler de manière inattendue et être utilisées de façon malveillante.
Créer des requêtes de chemin permet de visualiser le flux d’information dans une base de code. Une requête peut suivre le chemin que les données empruntent depuis leurs points de départ possibles (source) jusqu’à leurs points d’arrivée possibles (sink). Pour modéliser ces chemins, votre requête doit fournir des informations sur :
- la source,
- le sink (cible),
- les étapes du flux de données qui les relient.
La manière la plus simple de commencer à écrire votre propre requête de chemin est d’utiliser une requête existante comme modèle. Pour obtenir ces requêtes pour les langages pris en charge, consultez la documentation CodeQL.
Votre requête de chemin doit inclure :
- des métadonnées spécifiques,
- des prédicats de requête,
- une structure de clause
select
.
La plupart des requêtes de chemin intégrées dans CodeQL suivent une structure de base, qui dépend de la manière dont CodeQL modélise le langage que vous analysez.
/**
* ...
* @kind path-problem
* ...
*/
import <language>
// For some languages (Java/C++/Python/Swift), you need to explicitly import the data-flow library, such as
// import semmle.code.java.dataflow.DataFlow or import codeql.swift.dataflow.DataFlow
...
module Flow = DataFlow::Global<MyConfiguration>;
import Flow::PathGraph
from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "<message>"
Dans ce modèle :
MyConfiguration
est un module qui contient les prédicats définissant comment les données circulent entre la source et le sink (cible).Flow
est le résultat du calcul du flux de données basé surMyConfiguration
.Flow::PathGraph
est le module de graphe de flux de données que vous devez importer pour inclure les explications de chemin dans la requête.source
etsink
sont des nœuds du graphe définis dans la configuration, etFlow::PathNode
est leur type.DataFlow::Global<..>
est une invocation du flux de données. Vous pouvez utiliserTaintTracking::Global<..>
à la place pour inclure un ensemble par défaut d’étapes de contamination (taint steps).
Comment écrire une requête de chemin
Votre requête doit calculer un graphe de chemin pour générer des explications de chemin. Pour cela, vous devez définir un prédicat de requête appelé edges
. Un prédicat de requête est un prédicat non membre avec une annotation query
. Cette annotation retourne tous les tuples évalués par le prédicat.
Le prédicat edges
définit les relations d’arêtes du graphe que vous calculez. Il est utilisé pour déterminer les chemins liés à chaque résultat généré par votre requête. Vous pouvez aussi importer un prédicat edges
prédéfini depuis un module de graphe de chemin dans l’une des bibliothèques de flux de données standard.
Les bibliothèques de flux de données contiennent les autres classes, prédicats et modules couramment utilisés dans l’analyse de flux de données, en plus du module de graphe de chemin. Elles fonctionnent en modélisant le graphe de flux ou en implémentant l’analyse de flux. Les bibliothèques normales sont utilisées pour analyser le flux d’information où les valeurs de données sont conservées à chaque étape.
Voici un exemple d’instruction pour importer le module PathGraph
depuis la bibliothèque DataFlow.qll
, où edges
est défini :
import DataFlow::PathGraph
Vous pouvez importer de nombreuses autres bibliothèques incluses avec CodeQL. Il est également possible d’importer des bibliothèques conçues spécifiquement pour implémenter l’analyse de flux dans divers frameworks et environnements.
La classe PathNode
est conçue pour implémenter l’analyse de flux de données. Elle étend la classe Node
avec un contexte d’appel (sauf pour les sinks), un chemin d’accès, et une configuration. Seules les valeurs PathNode
accessibles depuis une source sont générées.
Voici un exemple de chemin d’importation :
import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl
Vous pouvez également définir un prédicat de requête nodes
(optionnel), qui spécifie les nœuds du graphe de chemin pour tous les langages. Lorsque vous définissez nodes
, les nœuds sélectionnés définissent uniquement les arêtes avec des points d’extrémité. Si vous ne définissez pas nodes
, vous devez sélectionner tous les points d’extrémité possibles des arêtes.
Analyse de la base de données
Lorsque vous utilisez des requêtes pour analyser une base de données CodeQL, vous obtenez des résultats significatifs dans le contexte du code source. Les résultats sont présentés sous forme d’alertes ou de chemins au format SARIF ou un autre format interprété.
Voici un exemple de commande CodeQL pour analyser une base de données en exécutant des requêtes sélectionnées et en interprétant les résultats :
codeql database analyze --format=<format> --output=<output> [--threads=<num>] [--ram=<MB>] <options>... -- <database> <query|dir|suite>...
Cette commande combine les effets des commandes internes codeql database run-queries
et codeql database interpret-results
.
Vous pouvez aussi exécuter des requêtes qui ne répondent pas aux critères pour être interprétées comme des alertes dans le code source. Pour cela, utilisez :
codeql database run-queries
ou
codeql query run
Puis utilisez :
codeql bqrs decode
pour convertir les résultats bruts en une notation lisible.
👉 Vous pouvez consulter la liste complète des commandes disponibles dans le manuel du CodeQL CLI.
Utiliser un fichier SARIF avec des catégories
CodeQL prend en charge SARIF pour le partage des résultats d’analyse statique. SARIF est conçu pour représenter les sorties de divers outils d’analyse statique.
Vous devez spécifier une catégorie lors de l’utilisation du format SARIF pour l’analyse CodeQL. Les catégories permettent de distinguer plusieurs analyses effectuées sur le même dépôt ou sur différentes parties du code. Cependant, les fichiers SARIF ayant la même catégorie écrasent les uns les autres.
Vous pouvez analyser différents langages dans une même base de code en conservant une valeur de catégorie cohérente entre les exécutions. Il est recommandé d’utiliser le langage analysé comme identifiant de catégorie.
Voici un exemple :
La valeur de catégorie apparaît comme :
run.automationId
dans SARIF v1,run.automationLogicalId
dans SARIF v2,run.automationDetails.id
dans SARIF v2.1.0.
Publier les résultats SARIF sur GitHub
Une fois la base de données prête, vous pouvez l’interroger de manière interactive ou exécuter une suite de requêtes pour générer des résultats au format SARIF, puis les téléverser vers un dépôt cible sur GitHub.com :
codeql github upload-results --sarif=<file> [--github-auth-stdin] [--github-url=<url>] [--repository=<repository-name>] [--ref=<ref>] [--commit=<commit>] [--checkout-path=<path>] <options>...
Pour téléverser les résultats sur GitHub, assurez-vous que chaque serveur d’intégration continue (CI) dispose d’une GitHub App ou d’un jeton d’accès personnel avec la permission security_events
en écriture.
👉 Il est possible d’utiliser le même jeton que celui utilisé par les serveurs CI pour cloner les dépôts GitHub. Sinon, créez un nouveau jeton avec la permission security_events
et ajoutez-le au stockage sécurisé du système CI.
Bonne pratique : utilisez l’option --github-auth-stdin
et passez le jeton via l’entrée standard.
Téléverser les résultats SARIF
Pour que l’analyse de code affiche les résultats d’un outil d’analyse statique non-Microsoft dans votre dépôt GitHub, les résultats doivent être stockés dans un fichier SARIF compatible avec un sous-ensemble spécifique du schéma JSON SARIF 2.1.0.
Chaque fois que vous téléversez les résultats d’une nouvelle analyse, CodeQL les traite et ajoute des alertes au dépôt. Pour éviter les doublons, l’analyse utilise la propriété partialFingerprints
de SARIF pour faire correspondre les résultats entre les différentes exécutions, afin qu’ils n’apparaissent qu’une seule fois dans la dernière exécution de la branche sélectionnée.
Le rule ID d’un résultat doit être identique entre les analyses. Les données d’empreinte sont automatiquement incluses dans les fichiers SARIF créés via le workflow d’analyse CodeQL ou le runner CodeQL.
La spécification SARIF utilise la propriété JSON partialFingerprints
, un dictionnaire associant des types d’empreintes nommés à leur valeur. Cette propriété contient, au minimum, une valeur pour primaryLocationLineHash
, qui fournit une empreinte basée sur le contexte de l’emplacement principal.
GitHub tente de remplir le champ partialFingerprints
à partir des fichiers source si vous téléversez un fichier SARIF via l’action upload-sarif
et que ces données sont absentes.
⚠️ Si vous téléversez un fichier SARIF sans données d’empreinte via l’API /code-scanning/sarifs
, des alertes dupliquées peuvent apparaître lors du traitement et de l’affichage.
👉 Pour éviter les doublons, calculez les empreintes et remplissez la propriété partialFingerprints
avant de téléverser.