Skip to content

Commit 40d98ad

Browse files
authored
Merge pull request github#6789 from asgerf/js/restrict-package-exports
Approved by erik-krogh
2 parents f230a37 + cbd5776 commit 40d98ad

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

javascript/ql/lib/semmle/javascript/PackageExports.qll

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ private DataFlow::Node getAValueExportedByPackage() {
3030
getAnExportFromModule(any(PackageJSON pack | exists(pack.getPackageName())).getMainModule())
3131
or
3232
// module.exports.bar.baz = result;
33-
result = getAValueExportedByPackage().(DataFlow::PropWrite).getRhs()
33+
exists(DataFlow::PropWrite write |
34+
write = getAValueExportedByPackage() and
35+
write.getPropertyName() = publicPropertyName() and
36+
result = write.getRhs()
37+
)
3438
or
3539
// class Foo {
3640
// bar() {} // <- result
@@ -39,15 +43,17 @@ private DataFlow::Node getAValueExportedByPackage() {
3943
exists(DataFlow::SourceNode callee |
4044
callee = getAValueExportedByPackage().(DataFlow::NewNode).getCalleeNode().getALocalSource()
4145
|
42-
result = callee.getAPropertyRead("prototype").getAPropertyWrite().getRhs()
46+
result = callee.getAPropertyRead("prototype").getAPropertyWrite(publicPropertyName()).getRhs()
4347
or
44-
result = callee.(DataFlow::ClassNode).getAnInstanceMethod()
48+
result = callee.(DataFlow::ClassNode).getInstanceMethod(publicPropertyName()) and
49+
not isPrivateMethodDeclaration(result)
4550
)
4651
or
4752
result = getAValueExportedByPackage().getALocalSource()
4853
or
4954
// Nested property reads.
50-
result = getAValueExportedByPackage().(DataFlow::SourceNode).getAPropertyReference()
55+
result =
56+
getAValueExportedByPackage().(DataFlow::SourceNode).getAPropertyReference(publicPropertyName())
5157
or
5258
// module.exports.foo = require("./other-module.js");
5359
exists(Module mod |
@@ -61,9 +67,12 @@ private DataFlow::Node getAValueExportedByPackage() {
6167
// static baz() {} // <- result
6268
// constructor() {} // <- result
6369
// };
64-
exists(DataFlow::ClassNode cla | cla = getAValueExportedByPackage() |
65-
result = cla.getAnInstanceMethod() or
66-
result = cla.getAStaticMethod() or
70+
exists(DataFlow::ClassNode cla |
71+
cla = getAValueExportedByPackage() and
72+
not isPrivateMethodDeclaration(result)
73+
|
74+
result = cla.getInstanceMethod(publicPropertyName()) or
75+
result = cla.getStaticMethod(publicPropertyName()) or
6776
result = cla.getConstructor()
6877
)
6978
or
@@ -120,7 +129,8 @@ private DataFlow::Node getAValueExportedByPackage() {
120129
or
121130
// Object.defineProperty
122131
exists(CallToObjectDefineProperty call |
123-
[call, call.getBaseObject()] = getAValueExportedByPackage()
132+
[call, call.getBaseObject()] = getAValueExportedByPackage() and
133+
call.getPropertyName() = publicPropertyName()
124134
|
125135
result = call.getPropertyDescriptor().getALocalSource().getAPropertyReference("value")
126136
or
@@ -164,9 +174,31 @@ private DataFlow::Node getAValueExportedByPackage() {
164174
* Gets an exported node from the module `mod`.
165175
*/
166176
private DataFlow::Node getAnExportFromModule(Module mod) {
167-
result = mod.getAnExportedValue(_)
177+
result = mod.getAnExportedValue(publicPropertyName())
168178
or
169179
result = mod.getABulkExportedNode()
170180
or
171181
result.analyze().getAValue() = TAbstractModuleObject(mod)
172182
}
183+
184+
/**
185+
* Gets a property name that we consider to be public.
186+
*
187+
* This only allows properties whose first character is a letter or number.
188+
*/
189+
bindingset[result]
190+
private string publicPropertyName() { result.regexpMatch("[a-zA-Z0-9].*") }
191+
192+
/**
193+
* Holds if the given function is part of a private (or protected) method declaration.
194+
*/
195+
private predicate isPrivateMethodDeclaration(DataFlow::FunctionNode func) {
196+
exists(MethodDeclaration decl |
197+
decl.getBody() = func.getFunction() and
198+
(
199+
decl.isPrivate()
200+
or
201+
decl.isProtected()
202+
)
203+
)
204+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export class PublicClass {
2+
protected constructor(p) {}
3+
private privateMethod(p) {}
4+
protected protectedMethod(p) {}
5+
_kindaPrivateMethod(p) {}
6+
$kindaPrivateMethod(p) {}
7+
#esPrivateMethod(p) {}
8+
9+
_kindaPrivateFieldMethod = (p) => {};
10+
private privateFieldMethod = (p) => {};
11+
}

javascript/ql/test/library-tests/PackageExports/tests.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ getAnExportedValue
1515
| lib1/reexport/a.js:1:1:3:1 | <toplevel> | reexported | lib1/reexport/a.js:2:17:2:40 | functio ... ed() {} |
1616
| lib1/reexport/b.js:1:1:6:1 | <toplevel> | base | lib1/reexport/b.js:4:11:4:28 | function base() {} |
1717
| lib1/reexport/b.js:1:1:6:1 | <toplevel> | reexported | lib1/reexport/a.js:2:17:2:40 | functio ... ed() {} |
18+
| notPublic.ts:1:1:12:0 | <toplevel> | PublicClass | notPublic.ts:1:8:11:1 | class P ... > {};\\n} |

0 commit comments

Comments
 (0)