Skip to content

Commit a7b39eb

Browse files
committed
Ruby: Flow through hash-splat parameters
1 parent 67572bb commit a7b39eb

File tree

7 files changed

+142
-2
lines changed

7 files changed

+142
-2
lines changed

ruby/ql/consistency-queries/DataFlowConsistency.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
99
n instanceof BlockArgumentNode
1010
or
1111
n instanceof SummaryNode
12+
or
13+
n instanceof HashSplatArgumentsNode
1214
}
1315
}

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ private module Cached {
259259
exists(any(Call c).getKeywordArgument(name))
260260
or
261261
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
262-
}
262+
} or
263+
THashSplatArgumentPosition()
263264

264265
cached
265266
newtype TParameterPosition =
@@ -278,6 +279,7 @@ private module Cached {
278279
or
279280
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
280281
} or
282+
THashSplatParameterPosition() or
281283
TAnyParameterPosition()
282284
}
283285

@@ -476,6 +478,9 @@ class ParameterPosition extends TParameterPosition {
476478
/** Holds if this position represents a keyword parameter named `name`. */
477479
predicate isKeyword(string name) { this = TKeywordParameterPosition(name) }
478480

481+
/** Holds if this position represents a hash-splat parameter. */
482+
predicate isHashSplat() { this = THashSplatParameterPosition() }
483+
479484
/**
480485
* Holds if this position represents any parameter. This includes both positional
481486
* and named parameters.
@@ -494,6 +499,8 @@ class ParameterPosition extends TParameterPosition {
494499
or
495500
exists(string name | this.isKeyword(name) and result = "keyword " + name)
496501
or
502+
this.isHashSplat() and result = "**"
503+
or
497504
this.isAny() and result = "any"
498505
}
499506
}
@@ -512,6 +519,12 @@ class ArgumentPosition extends TArgumentPosition {
512519
/** Holds if this position represents a keyword argument named `name`. */
513520
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
514521

522+
/**
523+
* Holds if this position represents a synthesized argument containing all keyword
524+
* arguments wrapped in a hash.
525+
*/
526+
predicate isHashSplat() { this = THashSplatArgumentPosition() }
527+
515528
/** Gets a textual representation of this position. */
516529
string toString() {
517530
this.isSelf() and result = "self"
@@ -521,6 +534,8 @@ class ArgumentPosition extends TArgumentPosition {
521534
exists(int pos | this.isPositional(pos) and result = "position " + pos)
522535
or
523536
exists(string name | this.isKeyword(name) and result = "keyword " + name)
537+
or
538+
this.isHashSplat() and result = "**"
524539
}
525540
}
526541

@@ -539,5 +554,7 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
539554
or
540555
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
541556
or
557+
ppos.isHashSplat() and apos.isHashSplat()
558+
or
542559
ppos.isAny() and exists(apos)
543560
}

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ private module Cached {
216216
TNormalParameterNode(Parameter p) {
217217
p instanceof SimpleParameter or
218218
p instanceof OptionalParameter or
219-
p instanceof KeywordParameter
219+
p instanceof KeywordParameter or
220+
p instanceof HashSplatParameter
220221
} or
221222
TSelfParameterNode(MethodBase m) or
222223
TBlockParameterNode(MethodBase m) or
@@ -232,6 +233,9 @@ private module Cached {
232233
} or
233234
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
234235
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
236+
} or
237+
THashSplatArgumentsNode(CfgNodes::ExprNodes::CallCfgNode c) {
238+
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
235239
}
236240

237241
class TParameterNode =
@@ -389,6 +393,8 @@ predicate nodeIsHidden(Node n) {
389393
n instanceof SummaryParameterNode
390394
or
391395
n instanceof SynthReturnNode
396+
or
397+
n instanceof HashSplatArgumentsNode
392398
}
393399

394400
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -473,6 +479,9 @@ private module ParameterNodes {
473479
c.getAParameter() = kp and
474480
pos.isKeyword(kp.getName())
475481
)
482+
or
483+
parameter = c.getAParameter().(HashSplatParameter) and
484+
pos.isHashSplat()
476485
}
477486

478487
override CfgScope getCfgScope() { result = parameter.getCallable() }
@@ -651,6 +660,40 @@ private module ArgumentNodes {
651660
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
652661
}
653662
}
663+
664+
/**
665+
* A data-flow node that represents all keyword arguments wrapped in a hash.
666+
*
667+
* The callee is responsible for filtering out the keyword arguments that are
668+
* part of the method signature, such that those cannot end up in the hash-splat
669+
* parameter.
670+
*/
671+
class HashSplatArgumentsNode extends ArgumentNode, THashSplatArgumentsNode {
672+
CfgNodes::ExprNodes::CallCfgNode c;
673+
674+
HashSplatArgumentsNode() { this = THashSplatArgumentsNode(c) }
675+
676+
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
677+
this.sourceArgumentOf(call.asCall(), pos)
678+
}
679+
680+
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
681+
call = c and
682+
pos.isHashSplat()
683+
}
684+
}
685+
686+
private class HashSplatArgumentsNodeImpl extends NodeImpl, THashSplatArgumentsNode {
687+
CfgNodes::ExprNodes::CallCfgNode c;
688+
689+
HashSplatArgumentsNodeImpl() { this = THashSplatArgumentsNode(c) }
690+
691+
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
692+
693+
override Location getLocationImpl() { result = c.getLocation() }
694+
695+
override string toStringImpl() { result = "**" }
696+
}
654697
}
655698

656699
import ArgumentNodes
@@ -807,6 +850,13 @@ predicate jumpStep(Node pred, Node succ) {
807850
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
808851
}
809852

853+
private ContentSet getKeywordContent(string name) {
854+
exists(ConstantValue::ConstantSymbolValue key |
855+
result.isSingleton(TKnownElementContent(key)) and
856+
key.isSymbol(name)
857+
)
858+
}
859+
810860
/**
811861
* Holds if data can flow from `node1` to `node2` via an assignment to
812862
* content `c`.
@@ -845,6 +895,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
845895
c.isSingleton(TUnknownPairValueContent())
846896
)
847897
)
898+
or
899+
// Wrap all keyword arguments in a synthesized hash-splat argument node
900+
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition keywordPos, string name |
901+
node2 = THashSplatArgumentsNode(call) and
902+
node1.asExpr().(Argument).isArgumentOf(call, keywordPos) and
903+
keywordPos.isKeyword(name) and
904+
c = getKeywordContent(name)
905+
)
848906
}
849907

850908
/**
@@ -870,6 +928,19 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
870928
*/
871929
predicate clearsContent(Node n, ContentSet c) {
872930
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
931+
or
932+
// Filter out keyword arguments that are part of the method signature from
933+
// the hash-splat parameter
934+
exists(
935+
DataFlowCallable callable, ParameterPosition hashSplatPos, ParameterNodeImpl keywordParam,
936+
ParameterPosition keywordPos, string name
937+
|
938+
n.(ParameterNodes::NormalParameterNode).isParameterOf(callable, hashSplatPos) and
939+
hashSplatPos.isHashSplat() and
940+
keywordParam.isParameterOf(callable, keywordPos) and
941+
keywordPos.isKeyword(name) and
942+
c = getKeywordContent(name)
943+
)
873944
}
874945

875946
/**

ruby/ql/test/library-tests/dataflow/params/params-flow.expected

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ edges
1212
| params_flow.rb:22:27:22:34 | call to taint : | params_flow.rb:16:13:16:14 | p1 : |
1313
| params_flow.rb:23:16:23:23 | call to taint : | params_flow.rb:16:18:16:19 | p2 : |
1414
| params_flow.rb:23:33:23:40 | call to taint : | params_flow.rb:16:13:16:14 | p1 : |
15+
| params_flow.rb:25:12:25:13 | p1 : | params_flow.rb:26:10:26:11 | p1 |
16+
| params_flow.rb:25:17:25:24 | **kwargs [element :p2] : | params_flow.rb:28:11:28:16 | kwargs [element :p2] : |
17+
| params_flow.rb:25:17:25:24 | **kwargs [element :p3] : | params_flow.rb:29:11:29:16 | kwargs [element :p3] : |
18+
| params_flow.rb:28:11:28:16 | kwargs [element :p2] : | params_flow.rb:28:11:28:21 | ...[...] : |
19+
| params_flow.rb:28:11:28:21 | ...[...] : | params_flow.rb:28:10:28:22 | ( ... ) |
20+
| params_flow.rb:29:11:29:16 | kwargs [element :p3] : | params_flow.rb:29:11:29:21 | ...[...] : |
21+
| params_flow.rb:29:11:29:21 | ...[...] : | params_flow.rb:29:10:29:22 | ( ... ) |
22+
| params_flow.rb:33:12:33:19 | call to taint : | params_flow.rb:25:12:25:13 | p1 : |
23+
| params_flow.rb:33:26:33:34 | call to taint : | params_flow.rb:25:17:25:24 | **kwargs [element :p2] : |
24+
| params_flow.rb:33:41:33:49 | call to taint : | params_flow.rb:25:17:25:24 | **kwargs [element :p3] : |
1525
nodes
1626
| params_flow.rb:9:16:9:17 | p1 : | semmle.label | p1 : |
1727
| params_flow.rb:9:20:9:21 | p2 : | semmle.label | p2 : |
@@ -29,6 +39,19 @@ nodes
2939
| params_flow.rb:22:27:22:34 | call to taint : | semmle.label | call to taint : |
3040
| params_flow.rb:23:16:23:23 | call to taint : | semmle.label | call to taint : |
3141
| params_flow.rb:23:33:23:40 | call to taint : | semmle.label | call to taint : |
42+
| params_flow.rb:25:12:25:13 | p1 : | semmle.label | p1 : |
43+
| params_flow.rb:25:17:25:24 | **kwargs [element :p2] : | semmle.label | **kwargs [element :p2] : |
44+
| params_flow.rb:25:17:25:24 | **kwargs [element :p3] : | semmle.label | **kwargs [element :p3] : |
45+
| params_flow.rb:26:10:26:11 | p1 | semmle.label | p1 |
46+
| params_flow.rb:28:10:28:22 | ( ... ) | semmle.label | ( ... ) |
47+
| params_flow.rb:28:11:28:16 | kwargs [element :p2] : | semmle.label | kwargs [element :p2] : |
48+
| params_flow.rb:28:11:28:21 | ...[...] : | semmle.label | ...[...] : |
49+
| params_flow.rb:29:10:29:22 | ( ... ) | semmle.label | ( ... ) |
50+
| params_flow.rb:29:11:29:16 | kwargs [element :p3] : | semmle.label | kwargs [element :p3] : |
51+
| params_flow.rb:29:11:29:21 | ...[...] : | semmle.label | ...[...] : |
52+
| params_flow.rb:33:12:33:19 | call to taint : | semmle.label | call to taint : |
53+
| params_flow.rb:33:26:33:34 | call to taint : | semmle.label | call to taint : |
54+
| params_flow.rb:33:41:33:49 | call to taint : | semmle.label | call to taint : |
3255
subpaths
3356
#select
3457
| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:14:12:14:19 | call to taint : | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:14:12:14:19 | call to taint : | call to taint : |
@@ -39,3 +62,6 @@ subpaths
3962
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:21:27:21:34 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:21:27:21:34 | call to taint : | call to taint : |
4063
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:22:13:22:20 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:22:13:22:20 | call to taint : | call to taint : |
4164
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:23:16:23:23 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:23:16:23:23 | call to taint : | call to taint : |
65+
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:33:12:33:19 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:33:12:33:19 | call to taint : | call to taint : |
66+
| params_flow.rb:28:10:28:22 | ( ... ) | params_flow.rb:33:26:33:34 | call to taint : | params_flow.rb:28:10:28:22 | ( ... ) | $@ | params_flow.rb:33:26:33:34 | call to taint : | call to taint : |
67+
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:33:41:33:49 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:33:41:33:49 | call to taint : | call to taint : |

ruby/ql/test/library-tests/dataflow/params/params_flow.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,13 @@ def keyword(p1:, p2:)
2121
keyword(p1: taint(3), p2: taint(4))
2222
keyword(p2: taint(5), p1: taint(6))
2323
keyword(:p2 => taint(7), :p1 => taint(8))
24+
25+
def kwargs(p1:, **kwargs)
26+
sink p1 # $ hasValueFlow=9
27+
sink (kwargs[:p1])
28+
sink (kwargs[:p2]) # $ hasValueFlow=10
29+
sink (kwargs[:p3]) # $ hasValueFlow=11
30+
sink (kwargs[:p4])
31+
end
32+
33+
kwargs(p1: taint(9), p2: taint(10), p3: taint(11), p4: "")

ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ track
139139
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword |
140140
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:32:1:32:27 | call to keyword |
141141
| type_tracker.rb:27:10:27:11 | [post] p2 | type tracker without call steps | type_tracker.rb:27:10:27:11 | [post] p2 |
142+
| type_tracker.rb:30:1:30:21 | ** | type tracker without call steps | type_tracker.rb:30:1:30:21 | ** |
142143
| type_tracker.rb:30:1:30:21 | [post] self | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
143144
| type_tracker.rb:30:1:30:21 | [post] self | type tracker with call steps | type_tracker.rb:25:1:28:3 | self in keyword |
144145
| type_tracker.rb:30:1:30:21 | [post] self | type tracker without call steps | type_tracker.rb:30:1:30:21 | [post] self |
@@ -155,6 +156,7 @@ track
155156
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
156157
| type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps | type_tracker.rb:30:20:30:20 | 4 |
157158
| type_tracker.rb:30:20:30:20 | [post] 4 | type tracker without call steps | type_tracker.rb:30:20:30:20 | [post] 4 |
159+
| type_tracker.rb:31:1:31:21 | ** | type tracker without call steps | type_tracker.rb:31:1:31:21 | ** |
158160
| type_tracker.rb:31:1:31:21 | [post] self | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
159161
| type_tracker.rb:31:1:31:21 | [post] self | type tracker with call steps | type_tracker.rb:25:1:28:3 | self in keyword |
160162
| type_tracker.rb:31:1:31:21 | [post] self | type tracker without call steps | type_tracker.rb:31:1:31:21 | [post] self |
@@ -171,6 +173,7 @@ track
171173
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
172174
| type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps | type_tracker.rb:31:20:31:20 | 6 |
173175
| type_tracker.rb:31:20:31:20 | [post] 6 | type tracker without call steps | type_tracker.rb:31:20:31:20 | [post] 6 |
176+
| type_tracker.rb:32:1:32:27 | ** | type tracker without call steps | type_tracker.rb:32:1:32:27 | ** |
174177
| type_tracker.rb:32:1:32:27 | [post] self | type tracker without call steps | type_tracker.rb:32:1:32:27 | [post] self |
175178
| type_tracker.rb:32:1:32:27 | call to keyword | type tracker without call steps | type_tracker.rb:32:1:32:27 | call to keyword |
176179
| type_tracker.rb:32:9:32:11 | :p2 | type tracker without call steps | type_tracker.rb:32:9:32:11 | :p2 |
@@ -393,6 +396,7 @@ trackEnd
393396
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:31:1:31:21 | call to keyword |
394397
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:32:1:32:27 | call to keyword |
395398
| type_tracker.rb:27:10:27:11 | [post] p2 | type_tracker.rb:27:10:27:11 | [post] p2 |
399+
| type_tracker.rb:30:1:30:21 | ** | type_tracker.rb:30:1:30:21 | ** |
396400
| type_tracker.rb:30:1:30:21 | [post] self | type_tracker.rb:25:1:28:3 | self (keyword) |
397401
| type_tracker.rb:30:1:30:21 | [post] self | type_tracker.rb:25:1:28:3 | self in keyword |
398402
| type_tracker.rb:30:1:30:21 | [post] self | type_tracker.rb:26:5:26:11 | self |
@@ -415,6 +419,7 @@ trackEnd
415419
| type_tracker.rb:30:20:30:20 | 4 | type_tracker.rb:27:10:27:11 | p2 |
416420
| type_tracker.rb:30:20:30:20 | 4 | type_tracker.rb:30:20:30:20 | 4 |
417421
| type_tracker.rb:30:20:30:20 | [post] 4 | type_tracker.rb:30:20:30:20 | [post] 4 |
422+
| type_tracker.rb:31:1:31:21 | ** | type_tracker.rb:31:1:31:21 | ** |
418423
| type_tracker.rb:31:1:31:21 | [post] self | type_tracker.rb:25:1:28:3 | self (keyword) |
419424
| type_tracker.rb:31:1:31:21 | [post] self | type_tracker.rb:25:1:28:3 | self in keyword |
420425
| type_tracker.rb:31:1:31:21 | [post] self | type_tracker.rb:26:5:26:11 | self |
@@ -436,6 +441,7 @@ trackEnd
436441
| type_tracker.rb:31:20:31:20 | 6 | type_tracker.rb:26:10:26:11 | p1 |
437442
| type_tracker.rb:31:20:31:20 | 6 | type_tracker.rb:31:20:31:20 | 6 |
438443
| type_tracker.rb:31:20:31:20 | [post] 6 | type_tracker.rb:31:20:31:20 | [post] 6 |
444+
| type_tracker.rb:32:1:32:27 | ** | type_tracker.rb:32:1:32:27 | ** |
439445
| type_tracker.rb:32:1:32:27 | [post] self | type_tracker.rb:32:1:32:27 | [post] self |
440446
| type_tracker.rb:32:1:32:27 | call to keyword | type_tracker.rb:32:1:32:27 | call to keyword |
441447
| type_tracker.rb:32:9:32:11 | :p2 | type_tracker.rb:32:9:32:11 | :p2 |

ruby/ql/test/query-tests/security/cwe-078/CommandInjection.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ edges
1212
| CommandInjection.rb:46:15:46:26 | ...[...] : | CommandInjection.rb:50:24:50:36 | "echo #{...}" |
1313
| CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" |
1414
| CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" |
15+
| CommandInjection.rb:81:20:81:25 | **args : | CommandInjection.rb:82:22:82:25 | args : |
16+
| CommandInjection.rb:82:22:82:25 | args : | CommandInjection.rb:82:22:82:37 | ...[...] : |
17+
| CommandInjection.rb:82:22:82:37 | ...[...] : | CommandInjection.rb:82:14:82:39 | "echo #{...}" |
1518
nodes
1619
| CommandInjection.rb:6:15:6:20 | call to params : | semmle.label | call to params : |
1720
| CommandInjection.rb:6:15:6:26 | ...[...] : | semmle.label | ...[...] : |
@@ -30,6 +33,10 @@ nodes
3033
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | semmle.label | "echo #{...}" |
3134
| CommandInjection.rb:72:23:72:33 | blah_number : | semmle.label | blah_number : |
3235
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | semmle.label | "echo #{...}" |
36+
| CommandInjection.rb:81:20:81:25 | **args : | semmle.label | **args : |
37+
| CommandInjection.rb:82:14:82:39 | "echo #{...}" | semmle.label | "echo #{...}" |
38+
| CommandInjection.rb:82:22:82:25 | args : | semmle.label | args : |
39+
| CommandInjection.rb:82:22:82:37 | ...[...] : | semmle.label | ...[...] : |
3340
subpaths
3441
#select
3542
| CommandInjection.rb:7:10:7:15 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
@@ -43,3 +50,4 @@ subpaths
4350
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:46:15:46:20 | call to params | a user-provided value |
4451
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:64:18:64:23 | number | a user-provided value |
4552
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:72:23:72:33 | blah_number | a user-provided value |
53+
| CommandInjection.rb:82:14:82:39 | "echo #{...}" | CommandInjection.rb:81:20:81:25 | **args : | CommandInjection.rb:82:14:82:39 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:81:20:81:25 | **args | a user-provided value |

0 commit comments

Comments
 (0)