@stoplight/spectral-owasp-ruleset
Version:
Probably don't want to beg hackers to come and take your stuff.
105 lines (95 loc) • 2.8 kB
text/typescript
import {
createRulesetFunction,
IFunctionResult,
} from "@stoplight/spectral-core";
/**
* @author Roberto Polli <https://github.com/ioggstream>
*/
const getAllOperations = function* (paths: any): any {
if (typeof paths !== "object") {
return;
}
const validMethods = [
"get",
"head",
"post",
"put",
"patch",
"delete",
"options",
"trace",
];
const operation = { path: "", operation: "" };
for (const idx of Object.keys(paths)) {
const path = paths[idx];
if (typeof path === "object") {
operation.path = idx;
for (const httpMethod of Object.keys(path)) {
typeof path[httpMethod] === "object" &&
validMethods.includes(httpMethod) &&
((operation.operation = httpMethod), yield operation);
}
}
}
};
export default createRulesetFunction(
{
input: null,
options: {
type: "object",
additionalProperties: false,
properties: {
adminUrl: {
type: "string",
},
},
required: [],
},
},
function differentSecuritySchemes(
input: any,
options: any
): IFunctionResult[] {
const errorList = [];
const nonAdminSecurityHashes = [];
const adminSecurityEntries = [];
const { adminUrl = "/admin" } = options;
const { paths } = input;
for (const { path, operation: httpMethod } of getAllOperations(paths)) {
let { security: operationSecurity } = paths[path][httpMethod];
// No security so skip this and leave it for other rules which check for security.
if (operationSecurity === undefined) {
continue;
}
if (Array.isArray(operationSecurity)) {
for (const [idx, securityEntry] of operationSecurity.entries()) {
if (typeof securityEntry !== "object") {
continue;
}
// This creates a string for easier comparison to see if its used elsewhere, but
// this might not be tough enough for some of the weirder edge cases.
const securityHash = JSON.stringify(securityEntry);
if (path.includes(adminUrl)) {
adminSecurityEntries.push({
uri: path,
hash: securityHash,
path: ["paths", path, httpMethod, "security", idx],
});
} else {
nonAdminSecurityHashes.push(securityHash);
}
}
}
}
// For every admin security entry, check if it is used in a non-admin path.
for (const { uri, hash, path } of adminSecurityEntries) {
if (nonAdminSecurityHashes.includes(hash)) {
errorList.push({
message: `Admin endpoint ${uri} has the same security requirement as a non-admin endpoint.`,
path,
});
}
}
return errorList;
}
);