Exécuter CodeQL sur une base de données
Une fois votre code extrait dans une base de données, vous pouvez l’analyser à l’aide de requêtes CodeQL. Les requêtes par défaut sont écrites et maintenues par des experts GitHub, des chercheurs en sécurité et des contributeurs de la communauté. Vous pouvez également écrire vos propres requêtes.
Les requêtes CodeQL sont utilisées dans l’analyse de code pour détecter des problèmes dans votre code source, y compris des vulnérabilités potentielles. Vous pouvez aussi écrire des requêtes personnalisées pour identifier des problèmes spécifiques à chaque langage utilisé dans votre projet.
Il existe deux types importants de requêtes :
- Requêtes d’alerte : elles mettent en évidence des problèmes à des emplacements précis dans votre code.
- Requêtes de chemin (path queries) : elles 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 est enregistrée dans un fichier avec l’extension .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 de requête
L’utilisation de CodeQL avec l’analyse de code (code scanning) transforme 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 interpréter les résultats. 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 de requête. Vous pouvez le consulter dans la documentation de CodeQL.
Voici un exemple de métadonnées pour l’une des requêtes standard en Java :
(L’exemple n’était pas inclus dans votre message. Souhaitez-vous que je vous en montre un ?)

CodeQL n’interprète pas les requêtes sans métadonnées
Les requêtes qui ne contiennent pas de métadonnées ne sont pas interprétées par CodeQL. Les résultats sont alors affichés sous forme de tableau, sans mise en évidence 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 les bases de données représentant des artefacts logiciels.
La syntaxe de QL est similaire à celle de SQL, mais sa sémantique repose sur Datalog, un langage de programmation logique déclaratif souvent utilisé comme langage de requête. Étant 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
, not
, ainsi que des quantificateurs comme forall
et exists
. Grâce aux prédicats récursifs, vous pouvez écrire des requêtes récursives complexes en utilisant la syntaxe de base de QL et des agrégats comme count
, sum
et average
.
Pour plus d’informations sur le langage QL, consultez la documentation de CodeQL.
Requêtes de chemin (Path Queries)
Le flux d’information dans un programme est crucial. Des données apparemment inoffensives peuvent circuler de manière inattendue et être utilisées de façon malveillante.
Les requêtes de chemin permettent de visualiser ce flux d’information dans une base de code. Une requête peut suivre le chemin que prennent les données depuis leurs points de départ possibles (sources) jusqu’à leurs points d’arrivée possibles (sinks). Pour modéliser ces chemins, votre requête doit définir :
- une source,
- un sink,
- les étapes de flux de données qui les relient.
Le moyen le plus simple de commencer à écrire une requête de chemin est d’utiliser une requête existante comme modèle. Pour obtenir ces requêtes selon les langages pris en charge, consultez la documentation de CodeQL.
Votre requête de chemin doit inclure certaines métadonnées, des prédicats de requête, et une structure d’instruction 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 analysé.
Voici un exemple de modèle de requête de chemin :
(L’exemple de code suit généralement ce paragraphe dans la documentation.)
/**
* ...
* @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 contenant les prédicats qui définissent comment les données circulent entre la source et le puits (sink).Flow
est le résultat du calcul de flux de données basé surMyConfiguration
.Flow::PathGraph
est le module de graphe de flux de données à 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 que le prédicat évalue.
Le prédicat edges
définit les relations d’arêtes du graphe que vous calculez. Il est utilisé pour déterminer les chemins associé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 d’autres classes, prédicats et modules couramment utilisés pour l’analyse de flux de données, en plus du module de graphe de chemin. Elles modélisent le graphe de flux de données ou implémentent l’analyse de flux. Les bibliothèques classiques sont utilisées pour analyser les flux 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, y compris celles conçues pour des frameworks ou environnements spécifiques.
La classe PathNode
La classe PathNode
est conçue pour implémenter l’analyse de flux de données. Elle est basée sur Node
, enrichie d’un contexte d’appel (sauf pour les sinks), d’un chemin d’accès et d’une configuration. Seuls les PathNode
atteignables depuis une source sont générés.
Exemple de chemin d’importation : import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl
Vous pouvez aussi définir un prédicat de requête nodes
, qui spécifie les nœuds du graphe de chemin pour tous les langages. Si vous définissez nodes
, seuls les nœuds sélectionnés formeront des arêtes. Si vous ne le définissez pas, vous devez sélectionner tous les points d’extrémité possibles des arêtes.
Analyse de base de données
Lorsque vous utilisez des requêtes pour analyser une base de données CodeQL, vous obtenez des résultats pertinents dans le contexte du code source. Les résultats sont présentés sous forme d’alertes ou de chemins au format SARIF ou autre format interprété.
Exemple de commande pour analyser une base de données CodeQL : codeql database analyze –format= –output= [–threads=] [–ram=] … — …
Cette commande combine les effets de codeql database run-queries
et codeql database interpret-results
.
Vous pouvez aussi exécuter des requêtes qui ne produisent pas d’alertes interprétables. Dans ce cas, utilisez : codeql database run-queries ou codeql query run
Puis convertissez les résultats bruts avec : codeql bqrs decode
Utiliser un fichier SARIF avec des catégories
CodeQL prend en charge SARIF pour partager les résultats d’analyse statique. SARIF permet de représenter les sorties de nombreux outils d’analyse.
Vous devez spécifier une catégorie lors de l’utilisation du format SARIF. Cela permet de distinguer plusieurs analyses sur le même dépôt ou sur différentes parties du code. Les fichiers SARIF avec la même catégorie se remplacent mutuellement.
Il est recommandé d’utiliser le langage analysé comme identifiant de catégorie.
Exemple : la valeur de catégorie apparaît dans :
run.automationId
(SARIF v1),run.automationLogicalId
(SARIF v2),run.automationDetails.id
(SARIF v2.1.0).
Publier les résultats SARIF sur GitHub
Une fois la base prête, vous pouvez interroger la base ou exécuter une suite de requêtes pour générer un fichier SARIF, puis l’envoyer sur GitHub : codeql github upload-results –sarif= [–github-auth-stdin] [–github-url=] [–repository=] [–ref=] [–commit=] [–checkout-path=] …
Pour cela, chaque serveur CI doit disposer d’un GitHub App ou d’un jeton d’accès personnel avec la permission security_events: write.
Téléverser les résultats SARIF
Pour que GitHub affiche les résultats d’un outil d’analyse statique non-Microsoft, les résultats doivent être dans un fichier SARIF conforme au schéma JSON SARIF 2.1.0.
À chaque téléversement, CodeQL traite les résultats et ajoute des alertes. Pour éviter les doublons, CodeQL utilise la propriété partialFingerprints
pour faire correspondre les résultats entre les exécutions.
Le champ partialFingerprints
contient au minimum une valeur pour primaryLocationLineHash
, qui fournit une empreinte basée sur le contexte de la ligne principale.
Si vous téléversez un fichier SARIF sans empreintes, GitHub tente de les générer automatiquement. Mais cela peut entraîner des alertes dupliquées.
Bonnes pratiques : calculez les empreintes et remplissez partialFingerprints
avant le téléversement. Vous pouvez vous baser sur le script utilisé par l’action upload-sarif
.
Pour éviter les alertes en double lors de l’utilisation d’outils d’analyse statique,
Calculez les empreintes numériques (fingerprints) et renseignez la propriété partialFingerprints
avant de téléverser le fichier SARIF.
Un bon point de départ consiste à utiliser le même script que celui utilisé par l’action upload-sarif
.