forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxsslint.js
More file actions
121 lines (102 loc) · 4.07 KB
/
Copy pathxsslint.js
File metadata and controls
121 lines (102 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
const XSSLint = require("xsslint");
const Linter = require("xsslint/linter");
const globby = require("gglobby");
const fs = require("fs");
const CoffeeScript = require("coffee-script");
const glob = require("glob");
const babylon = require("babylon");
XSSLint.configure({
"xssable.receiver.whitelist": ["formData"],
"jqueryObject.identifier": [/^\$/],
"jqueryObject.property": [/^\$/],
"safeString.identifier": [/(_html|Html|View|Template)$/, "html", "id"],
"safeString.function": ["h", "htmlEscape", "template", /(Template|View|Dialog)$/],
"safeString.property": ["template", "id", "height", "width", /_id$/],
"safeString.method": ["escapeContent", "$.raw", "template", /(Template|Html)$/, "toISOString", "friendlyDatetime", /^(date|(date)?time)String$/]
});
// treat I18n.t calls w/ wrappers as html-safe, since they are
const origIsSafeString = Linter.prototype.isSafeString;
Linter.prototype.isSafeString = function(node) {
const result = origIsSafeString.call(this, node);
if (result) return result;
if (node.type !== "CallExpression") return false;
const { type, object, property } = node.callee;
if (type !== "MemberExpression") return false;
if (object.type !== "Identifier" || object.name !== "I18n") return false;
if (property.type !== "Identifier" || property.name !== "t" && property.name !== "translate") return false;
const lastArg = node.arguments[node.arguments.length - 1];
if (lastArg.type !== "ObjectExpression") return false;
const hasWrapper = lastArg.properties.some((prop) => prop.key.name === "wrapper" || prop.key.name === "wrappers");
return hasWrapper;
};
function getFilesAndDirs(root, files = [], dirs = []) {
root = root === "." ? "" : root + "/";
const entries = fs.readdirSync(root || ".");
entries.forEach((entry) => {
const stats = fs.lstatSync(root + entry);
if (stats.isSymbolicLink()) {
} else if (stats.isDirectory()) {
dirs.push(root + entry + "/");
getFilesAndDirs(root + entry, files, dirs);
} else {
files.push(root + entry);
}
});
return [files, dirs];
}
function methodDescription(method) {
switch(method) {
case "+": return "HTML string concatenation";
case "`": return "HTML template literal";
default: return `argument to \`${method}\``;
}
}
const cwd = process.cwd();
let warningCount = 0;
const allPaths = [
{
paths: ["app/coffeescripts"].concat(glob.sync("gems/plugins/*/app/coffeescripts")),
glob: "*.coffee",
transform: (source) => CoffeeScript.compile(source, {})
},
{
paths: ["app/jsx"].concat(glob.sync("gems/plugins/*/app/jsx")),
glob: "*.jsx"
},
{
paths: ["public/javascripts"].concat(glob.sync("gems/plugins/*/public/javascripts")),
defaultIgnores: ['/compiled', '/jst', '/vendor'],
glob: "*.js"
}
]
allPaths.forEach(function({paths, glob, defaultIgnores = [], transform}) {
paths.forEach(function(path) {
process.chdir(path);
const ignores = defaultIgnores.concat(
fs.existsSync(".xssignore") ?
fs.readFileSync(".xssignore").toString().trim().split(/\r?\n|\r/) :
[]
);
let candidates = getFilesAndDirs(".");
candidates = {files: candidates[0], dirs: candidates[1]};
const files = globby.select([glob], candidates).reject(ignores).files;
console.log(`Checking ${path} (${files.length} files) for potential XSS vulnerabilities...`);
files.forEach(function(file) {
let source = fs.readFileSync(file).toString();
if (transform) source = transform(source);
source = babylon.parse(source, { plugins: ["jsx", "classProperties", "objectRestSpread"], sourceType: "module" });
const warnings = XSSLint.run({source});
warningCount += warnings.length;
warnings.forEach(({line, method}) => {
console.error(`${path}/${file}:${line}: possibly XSS-able ${methodDescription(method)}`);
})
});
process.chdir(cwd);
})
});
if (warningCount) {
console.error(`\u{1b}[31mFound ${warningCount} potential vulnerabilities\u{1b}[0m`);
process.exit(1)
} else {
console.log("No problems found!")
}