add model for the joi library

This commit is contained in:
Erik Krogh Kristensen
2021-06-07 20:04:17 +02:00
parent d254524f3f
commit be7abede22
8 changed files with 131 additions and 3 deletions

View File

@@ -8,8 +8,8 @@ import javascript
* Provides classes and predicates for working with JSON schema libraries.
*/
module JsonSchema {
/** A call that validates an input against a JSON schema. */
abstract class ValidationCall extends DataFlow::CallNode {
/** A node that validates an input against a JSON schema. */
abstract class ValidationCall extends DataFlow::Node {
/** Gets the data flow node whose value is being validated. */
abstract DataFlow::Node getInput();
@@ -89,7 +89,7 @@ module JsonSchema {
}
/** A call to the `validate` method of `ajv`. */
class AjvValidationCall extends ValidationCall {
class AjvValidationCall extends ValidationCall, DataFlow::CallNode {
Instance instance;
int argIndex;
@@ -126,4 +126,55 @@ module JsonSchema {
}
}
}
/** Provides a model for working with the [`joi`](https://npmjs.org/package/joi) library. */
module Joi {
/** A schema created using `joi.object()` or other schemas that might refer an object schema. */
private API::Node objectSchema() {
// A call that creates a schema that might be an object schema.
result =
API::moduleImport("joi")
.getMember([
"object", "alternatives", "all", "link", "compile", "allow", "valid", "when",
"build", "options"
])
.getReturn()
or
// A call to a schema that returns another schema.
// Read from the [index.d.ts](https://github.com/sideway/joi/blob/master/lib/index.d.ts) file.
result =
objectSchema()
.getMember([
// AnySchema
"allow", "alter", "bind", "cache", "cast", "concat", "default", "description",
"disallow", "empty", "equal", "error", "example", "exist", "external", "failover",
"forbidden", "fork", "id", "invalid", "keep", "label", "message", "messages",
"meta", "not", "note", "only", "optional", "options", "prefs", "preferences",
"presence", "raw", "required", "rule", "shared", "strict", "strip", "tag", "tailor",
"unit", "valid", "warn", "warning", "when",
// ObjectSchema
"and", "append", "assert", "instance", "keys", "length", "max", "min", "nand", "or",
"oxor", "pattern", "ref", "regex", "rename", "schema", "unknown", "with", "without",
"xor"
])
.getReturn()
}
/**
* A read of the `error` property from a validation result, seen as a `ValidationCall`.
* If `error` exists, then the validation failed.
*/
class JoiValidationErrorRead extends ValidationCall {
API::CallNode validateCall;
JoiValidationErrorRead() {
validateCall = objectSchema().getMember("validate").getACall() and
this = validateCall.getReturn().getMember("error").getAnImmediateUse()
}
override DataFlow::Node getInput() { result = validateCall.getArgument(0) }
override boolean getPolarity() { result = false }
}
}
}

View File

@@ -617,6 +617,8 @@ module ExceptionXss {
private class JsonSchemaValidationError extends Source {
JsonSchemaValidationError() {
this = any(JsonSchema::Ajv::Instance i).getAValidationError().getAnImmediateUse()
or
this = any(JsonSchema::Joi::JoiValidationErrorRead r)
}
override string getDescription() { result = "JSON schema validation error" }

View File

@@ -2,6 +2,9 @@ nodes
| ajv.js:11:18:11:33 | ajv.errorsText() |
| ajv.js:11:18:11:33 | ajv.errorsText() |
| ajv.js:11:18:11:33 | ajv.errorsText() |
| ajv.js:24:18:24:26 | val.error |
| ajv.js:24:18:24:26 | val.error |
| ajv.js:24:18:24:26 | val.error |
| exception-xss.js:2:6:2:28 | foo |
| exception-xss.js:2:12:2:28 | document.location |
| exception-xss.js:2:12:2:28 | document.location |
@@ -89,6 +92,7 @@ nodes
| exception-xss.js:182:19:182:23 | error |
edges
| ajv.js:11:18:11:33 | ajv.errorsText() | ajv.js:11:18:11:33 | ajv.errorsText() |
| ajv.js:24:18:24:26 | val.error | ajv.js:24:18:24:26 | val.error |
| exception-xss.js:2:6:2:28 | foo | exception-xss.js:9:11:9:13 | foo |
| exception-xss.js:2:6:2:28 | foo | exception-xss.js:15:9:15:11 | foo |
| exception-xss.js:2:6:2:28 | foo | exception-xss.js:21:11:21:13 | foo |
@@ -170,6 +174,7 @@ edges
| exception-xss.js:180:26:180:30 | error | exception-xss.js:182:19:182:23 | error |
#select
| ajv.js:11:18:11:33 | ajv.errorsText() | ajv.js:11:18:11:33 | ajv.errorsText() | ajv.js:11:18:11:33 | ajv.errorsText() | $@ is reinterpreted as HTML without escaping meta-characters. | ajv.js:11:18:11:33 | ajv.errorsText() | JSON schema validation error |
| ajv.js:24:18:24:26 | val.error | ajv.js:24:18:24:26 | val.error | ajv.js:24:18:24:26 | val.error | $@ is reinterpreted as HTML without escaping meta-characters. | ajv.js:24:18:24:26 | val.error | JSON schema validation error |
| exception-xss.js:11:18:11:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:11:18:11:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:17:18:17:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:17:18:17:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |
| exception-xss.js:23:18:23:18 | e | exception-xss.js:2:12:2:28 | document.location | exception-xss.js:23:18:23:18 | e | $@ is reinterpreted as HTML without escaping meta-characters. | exception-xss.js:2:12:2:28 | document.location | Exception text |

View File

@@ -11,3 +11,16 @@ app.post('/polldata', (req, res) => {
res.send(ajv.errorsText()); // NOT OK
}
});
const joi = require("joi");
const joiSchema = joi.object().keys({
name: joi.string().required(),
age: joi.number().required()
}).with('name', 'age');
app.post('/votedata', (req, res) => {
const val = joiSchema.validate(req.body);
if (val.error) {
res.send(val.error); // NOT OK
}
});

View File

@@ -2,6 +2,10 @@
| json-schema-validator.js:30:13:30:27 | doc.find(query) |
| json-schema-validator.js:33:13:33:27 | doc.find(query) |
| json-schema-validator.js:35:9:35:23 | doc.find(query) |
| json-schema-validator.js:53:13:53:27 | doc.find(query) |
| json-schema-validator.js:55:13:55:27 | doc.find(query) |
| json-schema-validator.js:59:13:59:27 | doc.find(query) |
| json-schema-validator.js:61:13:61:27 | doc.find(query) |
| marsdb-flow-to.js:14:3:14:22 | db.myDoc.find(query) |
| marsdb.js:16:3:16:17 | doc.find(query) |
| minimongo.js:18:3:18:17 | doc.find(query) |

View File

@@ -7,6 +7,16 @@ nodes
| json-schema-validator.js:33:22:33:26 | query |
| json-schema-validator.js:35:18:35:22 | query |
| json-schema-validator.js:35:18:35:22 | query |
| json-schema-validator.js:50:15:50:48 | query |
| json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
| json-schema-validator.js:50:34:50:47 | req.query.data |
| json-schema-validator.js:50:34:50:47 | req.query.data |
| json-schema-validator.js:55:22:55:26 | query |
| json-schema-validator.js:55:22:55:26 | query |
| json-schema-validator.js:59:22:59:26 | query |
| json-schema-validator.js:59:22:59:26 | query |
| json-schema-validator.js:61:22:61:26 | query |
| json-schema-validator.js:61:22:61:26 | query |
| marsdb-flow-to.js:10:9:10:18 | query |
| marsdb-flow-to.js:10:17:10:18 | {} |
| marsdb-flow-to.js:11:17:11:24 | req.body |
@@ -329,6 +339,15 @@ edges
| json-schema-validator.js:25:23:25:48 | JSON.pa ... y.data) | json-schema-validator.js:25:15:25:48 | query |
| json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:25:23:25:48 | JSON.pa ... y.data) |
| json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:25:23:25:48 | JSON.pa ... y.data) |
| json-schema-validator.js:50:15:50:48 | query | json-schema-validator.js:55:22:55:26 | query |
| json-schema-validator.js:50:15:50:48 | query | json-schema-validator.js:55:22:55:26 | query |
| json-schema-validator.js:50:15:50:48 | query | json-schema-validator.js:59:22:59:26 | query |
| json-schema-validator.js:50:15:50:48 | query | json-schema-validator.js:59:22:59:26 | query |
| json-schema-validator.js:50:15:50:48 | query | json-schema-validator.js:61:22:61:26 | query |
| json-schema-validator.js:50:15:50:48 | query | json-schema-validator.js:61:22:61:26 | query |
| json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) | json-schema-validator.js:50:15:50:48 | query |
| json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
| json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
| marsdb-flow-to.js:10:17:10:18 | {} | marsdb-flow-to.js:10:9:10:18 | query |
@@ -723,6 +742,9 @@ edges
#select
| json-schema-validator.js:33:22:33:26 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:33:22:33:26 | query | This query depends on $@. | json-schema-validator.js:25:34:25:47 | req.query.data | a user-provided value |
| json-schema-validator.js:35:18:35:22 | query | json-schema-validator.js:25:34:25:47 | req.query.data | json-schema-validator.js:35:18:35:22 | query | This query depends on $@. | json-schema-validator.js:25:34:25:47 | req.query.data | a user-provided value |
| json-schema-validator.js:55:22:55:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:55:22:55:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
| json-schema-validator.js:59:22:59:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:59:22:59:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
| json-schema-validator.js:61:22:61:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:61:22:61:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
| marsdb-flow-to.js:14:17:14:21 | query | marsdb-flow-to.js:11:17:11:24 | req.body | marsdb-flow-to.js:14:17:14:21 | query | This query depends on $@. | marsdb-flow-to.js:11:17:11:24 | req.body | a user-provided value |
| marsdb.js:16:12:16:16 | query | marsdb.js:13:17:13:24 | req.body | marsdb.js:16:12:16:16 | query | This query depends on $@. | marsdb.js:13:17:13:24 | req.body | a user-provided value |
| minimongo.js:18:12:18:16 | query | minimongo.js:15:17:15:24 | req.body | minimongo.js:18:12:18:16 | query | This query depends on $@. | minimongo.js:15:17:15:24 | req.body | a user-provided value |

View File

@@ -35,3 +35,30 @@ app.post('/documents/find', (req, res) => {
doc.find(query); // NOT OK
});
});
import Joi from 'joi';
const joiSchema = Joi.object({
date: Joi.string().required(),
title: Joi.string().required()
}).with('date', 'title');
app.post('/documents/insert', (req, res) => {
MongoClient.connect('mongodb://localhost:27017/test', async (err, db) => {
let doc = db.collection('doc');
const query = JSON.parse(req.query.data);
const validate = joiSchema.validate(query);
if (!validate.error) {
doc.find(query); // OK
} else {
doc.find(query); // NOT OK
}
try {
await joiSchema.validateAsync(query);
doc.find(query); // OK - but still flagged [INCONSISTENCY]
} catch (e) {
doc.find(query); // NOT OK
}
});
});