Skip to content

Commit d045392

Browse files
authored
Merge pull request #22264 from asgerf/js/file-local-model
JS: Add support for file-scoped MaD models
2 parents 70bfecc + b453630 commit d045392

7 files changed

Lines changed: 64 additions & 0 deletions

File tree

docs/codeql/codeql-language-guides/customizing-library-models-for-javascript.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ A type can be defined by adding ``typeModel`` tuples for that type. Additionally
549549
- The name of an NPM package matches imports of that package. For example, the type ``express`` matches the expression ``require("express")``. If the package name includes dots, it must be surrounded by single quotes, such as in ``'lodash.escape'``.
550550
- The type ``global`` identifies the global object, also known as ``window``. In JavaScript, global variables are properties of the global object, so global variables can be identified using this type. (This type also matches imports of the NPM package named ``global``, which is a package that happens to export the global object.)
551551
- A qualified type name of form ``<package>.<type>`` identifies expressions of type ``<type>`` from ``<package>``. For example, ``mysql.Connection`` identifies expression of type ``Connection`` from the ``mysql`` package. Note that this only works if type annotations are present in the codebase, or if sufficient ``typeModel`` tuples have been provided for that type.
552+
- A string of form ``file:<path>`` identifies expressions that are imported from a file at the given path. The path is relative to the root of the codebase, must use forward slashes as path separator, must include the file extension, and is case-sensitive. For example, ``file:src/utils.js`` identifies expressions such as ``require('./utils')`` inside ``src/``, or ``require('../src/utils')`` inside another top-level folder.
552553

553554
Access paths
554555
------------
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* It is now possible for custom models to refer to specific files in the codebase, using a package name of form `file:<path>`. The model should describe the public exports
5+
of that file. This can be used to derive sources and sinks in code that imports the file, but note that sources and sinks will not generally be placed within the file itself.
6+
For example, a source model `['file:lib/service.js', 'Member[getData].ReturnValue', 'remote']` could identify `require('../lib/service').getData()` as a source.

javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ predicate parseTypeString(string rawType, string package, string qualifiedName)
5353
qualifiedName = ""
5454
}
5555

56+
/** If `type` has the form `file:<path>` gets the path to the file. */
57+
bindingset[type]
58+
overlay[caller]
59+
private string getRawFilePathFromTypeName(string type) {
60+
result = type.regexpCapture("file:(.*)", 1)
61+
}
62+
5663
/**
5764
* Holds if models describing `package` may be relevant for the analysis of this database.
5865
*/
@@ -76,6 +83,8 @@ predicate isTypeUsed(string type) {
7683
parseTypeString(type, package, _) and
7784
isPackageUsed(package)
7885
)
86+
or
87+
exists(getRawFilePathFromTypeName(type)) // No need to prune repository-specific models
7988
}
8089

8190
/**
@@ -126,6 +135,41 @@ private API::Node getGlobalNode(string globalName) {
126135
result = any(GlobalApiEntryPoint e | e.getGlobal() = globalName).getANode()
127136
}
128137

138+
/** Holds if `type` is used as a type string in a model, and has the form `file:<filePath>` */
139+
overlay[local]
140+
private predicate relevantRawFilePath(string type, string filePath) {
141+
isRelevantType(type) and
142+
filePath = getRawFilePathFromTypeName(type)
143+
}
144+
145+
/** An API graph entry point for package specifiers of form `file:<path>`. */
146+
overlay[local?]
147+
private class RawFilePathEntryPoint extends API::EntryPoint {
148+
string path;
149+
150+
RawFilePathEntryPoint() {
151+
relevantRawFilePath(_, path) and
152+
this = "RawFilePathEntryPoint:" + path
153+
}
154+
155+
override DataFlow::SourceNode getASource() {
156+
exists(JS::Import imprt |
157+
imprt.getImportedFile().getRelativePath() = path and
158+
result = imprt.getImportedModuleNode()
159+
)
160+
}
161+
162+
/** Gets the file path being referenced. */
163+
string getPath() { result = path }
164+
}
165+
166+
/**
167+
* Gets an API node referring to the given file path (if relevant).
168+
*/
169+
private API::Node getRawFilePathNode(string rawFilePathNode) {
170+
result = any(RawFilePathEntryPoint e | e.getPath() = rawFilePathNode).getANode()
171+
}
172+
129173
/** Gets a JavaScript-specific interpretation of the `(type, path)` tuple after resolving the first `n` access path tokens. */
130174
bindingset[type, path]
131175
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
@@ -150,6 +194,11 @@ API::Node getExtraNodeFromType(string type) {
150194
// Access instance of a type based on type annotations
151195
result = API::Internal::getANodeOfTypeRaw(package, qualifiedName)
152196
)
197+
or
198+
exists(string filePath |
199+
relevantRawFilePath(type, filePath) and
200+
result = getRawFilePathNode(filePath)
201+
)
153202
}
154203

155204
/**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as bar from './foo/bar/baz';
2+
3+
function t1() {
4+
sink(bar.customSource()); // NOT OK
5+
}

javascript/ql/test/library-tests/frameworks/data/test.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ taintFlow
55
| guardedRouteHandler.js:10:10:10:28 | res.injectedResData | guardedRouteHandler.js:10:10:10:28 | res.injectedResData |
66
| guardedRouteHandler.js:16:10:16:28 | req.injectedReqData | guardedRouteHandler.js:16:10:16:28 | req.injectedReqData |
77
| guardedRouteHandler.js:20:10:20:28 | res.injectedResData | guardedRouteHandler.js:20:10:20:28 | res.injectedResData |
8+
| importFileBasedModel.js:4:10:4:27 | bar.customSource() | importFileBasedModel.js:4:10:4:27 | bar.customSource() |
89
| paramDecorator.ts:6:54:6:54 | x | paramDecorator.ts:7:10:7:10 | x |
910
| test.js:5:30:5:37 | source() | test.js:5:8:5:38 | testlib ... urce()) |
1011
| test.js:6:22:6:29 | source() | test.js:6:8:6:30 | preserv ... urce()) |

javascript/ql/test/library-tests/frameworks/data/test.ext.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extensions:
1515
- ['danger-constant', 'Member[danger]', 'test-source']
1616
- ['testlib', 'Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[0].Member[injectedReqData]', 'test-source']
1717
- ['testlib', 'Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[1].Member[injectedResData]', 'test-source']
18+
- ['file:foo/bar/baz.js', 'Member[customSource].ReturnValue', 'test-source']
1819

1920
- addsTo:
2021
pack: codeql/javascript-all

0 commit comments

Comments
 (0)