Skip to content

Commit 91f9e89

Browse files
authored
Merge pull request #10405 from erik-krogh/styleGuide
update the style guide on alert-messages
2 parents cd71546 + abb5c38 commit 91f9e89

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

docs/query-metadata-style-guide.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,15 @@ The select clause of each alert query defines the alert message that is displaye
179179
* The message should factually describe the problem that is being highlighted–it should not contain recommendations about how to fix the problem or value judgements.
180180
* Program element references should be in 'single quotes' to distinguish them from ordinary words. Quotes are not needed around substitutions (`$@`).
181181
* Avoid constant alert message strings and include some context, if possible. For example, `The class 'Foo' is duplicated as 'Bar'.` is preferable to `This class is duplicated here.`
182+
* If a reference to the current location can't be avoided use "this location" instead of "here". For example, `Bad thing at this location.` is preferable to `Bad thing here.`. This avoids the "click here" anti-pattern.
183+
* For path queries, if possible, try to follow the template: `This path depends on a [user-provided value].`, or alternatively (if the first option doesn't work) `[User-provided value] flows to this location and is used in a path.`.
184+
* Taint tracking queries generally have a sink that "depends on" the source, and dataflow queries generally have a source that "flows to" the sink.
185+
186+
### Links in alert messages
187+
182188
* Where you reference another program element, link to it if possible using a substitution (`$@`). Links should be used inline in the sentence, rather than as parenthesised lists or appositions.
189+
* Avoid using link texts that don't describe what they link to. For example, rewrite `This sensitive data is written to a logfile unescaped [here]` to `This sensitive data is [written to a logfile unescaped]`.
190+
* Make link text as concise and precise as possible. For example, avoid starting a link text with an indefinite article (a, an). `Path construction depends on a [user-provided value]` is preferable to `Path construction depends on [a user-provided value]`. (Where the square brackets indicate a link.) See [the W3C guide on link texts](https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html) for further information.
183191
* When a message contains multiple links, construct a sentence that has the most variable link (that is, the link with most targets) last. For further information, see [Defining the results of a query](https://codeql.github.com/docs/writing-codeql-queries/defining-the-results-of-a-query/).
184192

185193
For examples of select clauses and alert messages, see the query source files at the following pages:
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/**
2+
* @name Alert message style violation
3+
* @description An alert message that doesn't follow the style guide is harder for end users to digest.
4+
* See the style guide here: https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md#alert-messages
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id ql/alert-message-style-violation
8+
* @precision high
9+
*/
10+
11+
import ql
12+
13+
/** Gets the `index`th part of the select statement. */
14+
private AstNode getSelectPart(Select sel, int index) {
15+
result =
16+
rank[index](AstNode n, Location loc |
17+
(
18+
n.getParent*() = sel.getExpr(_) and loc = n.getLocation()
19+
or
20+
// the strings are behind a predicate call.
21+
exists(Call c, Predicate target |
22+
c.getParent*() = sel.getExpr(_) and loc = c.getLocation()
23+
|
24+
c.getTarget() = target and
25+
(
26+
target.getBody().(ComparisonFormula).getAnOperand() = n
27+
or
28+
exists(ClassPredicate sub | sub.overrides(target) |
29+
sub.getBody().(ComparisonFormula).getAnOperand() = n
30+
)
31+
)
32+
)
33+
)
34+
|
35+
n
36+
order by
37+
loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(),
38+
loc.getFile().getRelativePath()
39+
)
40+
}
41+
42+
/**
43+
* Gets a string element that is the last part of the message, that doesn't end with a period.
44+
*
45+
* For example:
46+
* ```CodeQL
47+
* select foo(), "This is a description" // <- bad
48+
*
49+
* select foo(), "This is a description." // <- good
50+
* ```
51+
*/
52+
String shouldHaveFullStop(Select sel) {
53+
result =
54+
max(AstNode str, int i |
55+
str.getParent+() = sel.getExpr(1) and str = getSelectPart(sel, i)
56+
|
57+
str order by i
58+
) and
59+
not result.getValue().matches("%.") and
60+
not result.getValue().matches("%?")
61+
}
62+
63+
/**
64+
* Gets a string element that is the first part of the message, that starts with a lower case letter.
65+
*
66+
* For example:
67+
* ```CodeQL
68+
* select foo(), "this is a description." // <- bad
69+
*
70+
* select foo(), "This is a description." // <- good
71+
* ```
72+
*/
73+
String shouldStartCapital(Select sel) {
74+
result =
75+
min(AstNode str, int i |
76+
str.getParent+() = sel.getExpr(1) and str = getSelectPart(sel, i)
77+
|
78+
str order by i
79+
) and
80+
result.getValue().regexpMatch("^[a-z].*")
81+
}
82+
83+
/**
84+
* Gets a string element that is used in a message that contains "here" or "this location".
85+
*
86+
* For example:
87+
* ```CodeQL
88+
* select foo(), "XSS happens here from using a unsafe value." // <- bad
89+
*
90+
* select foo(), "XSS from using a unsafe value." // <- good
91+
* ```
92+
*/
93+
String avoidHere(string part) {
94+
part = ["here", "this location"] and
95+
(
96+
result.getValue().regexpMatch(".*\\b" + part + "\\b.*") and
97+
result = getSelectPart(_, _)
98+
)
99+
}
100+
101+
/**
102+
* Avoid using an indefinite article ("a" or "an") in a link text.
103+
*
104+
* For example:
105+
* ```CodeQL
106+
* select foo(), "XSS from $@", val, "an unsafe value." // <- bad
107+
*
108+
* select foo(), "XSS from a $@", val, "unsafe value." // <- good
109+
* ```
110+
*
111+
* See https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html for the W3C guideline on link text. a
112+
*/
113+
String avoidArticleInLinkText(Select sel) {
114+
result = sel.getExpr((any(int i | i > 1))) and
115+
result = getSelectPart(sel, _) and
116+
result.getValue().regexpMatch("a|an .*")
117+
}
118+
119+
/**
120+
* Don't quote substitutions in a message.
121+
*
122+
* For example:
123+
* ```CodeQL
124+
* select foo(), "XSS from '$@'", val, "an unsafe value." // <- bad
125+
*
126+
* select foo(), "XSS from $@", val, "an unsafe value." // <- good
127+
* ```
128+
*/
129+
String dontQuoteSubstitutions(Select sel) {
130+
result = getSelectPart(sel, _) and
131+
result.getValue().matches(["%'$@'%", "%\"$@\"%"])
132+
}
133+
134+
/**
135+
* Gets the kind of the path-query represented by `sel`.
136+
* Either "data" for a dataflow query or "taint" for a taint-tracking query.
137+
*/
138+
private string getQueryKind(Select sel) {
139+
exists(TypeExpr sup |
140+
sup = sel.getVarDecl(_).getType().(ClassType).getDeclaration().getASuperType() and
141+
sup.getResolvedType().(ClassType).getName() = "Configuration"
142+
|
143+
result = "data" and
144+
sup.getModule().getName() = "DataFlow"
145+
or
146+
result = "taint" and
147+
sup.getModule().getName() = "TaintTracking"
148+
)
149+
}
150+
151+
/**
152+
* Gets a string element from a message that uses the wrong phrase for a path query.
153+
* A dataflow query should use "flows to" and a taint-tracking query should use "depends on".
154+
*/
155+
String wrongFlowsPhrase(Select sel, string kind) {
156+
result = getSelectPart(sel, _) and
157+
kind = getQueryKind(sel) and
158+
(
159+
kind = "data" and
160+
result.getValue().matches(["% depends %", "% depend %"])
161+
or
162+
kind = "taint" and
163+
result.getValue().matches(["% flows to %", "% flow to %"])
164+
)
165+
}
166+
167+
from AstNode node, string msg
168+
where
169+
not node.getLocation().getFile().getAbsolutePath().matches("%/test/%") and
170+
(
171+
node = shouldHaveFullStop(_) and
172+
msg = "Alert message should end with a full stop."
173+
or
174+
node = shouldStartCapital(_) and
175+
msg = "Alert message should start with a capital letter."
176+
or
177+
exists(string part | node = avoidHere(part) |
178+
part = "here" and
179+
msg =
180+
"Try to use a descriptive phrase instead of \"here\". Use \"this location\" if you can't get around mentioning the current location."
181+
or
182+
part = "this location" and
183+
msg = "Try to more descriptive phrase instead of \"this location\" if possible."
184+
)
185+
or
186+
node = avoidArticleInLinkText(_) and
187+
msg = "Avoid starting a link text with an indefinite article."
188+
or
189+
node = dontQuoteSubstitutions(_) and
190+
msg = "Don't quote substitutions in alert messages."
191+
or
192+
node = wrongFlowsPhrase(_, "data") and
193+
msg = "Use \"flows to\" instead of \"depends on\" in data flow queries."
194+
or
195+
node = wrongFlowsPhrase(_, "taint") and
196+
msg = "Use \"depends on\" instead of \"flows to\" in taint tracking queries."
197+
)
198+
select node, msg

0 commit comments

Comments
 (0)