eslint-plugin-jwt-security 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/index.js +1 -118
- package/src/oxlint.js +1 -3
- package/src/rules/no-algorithm-confusion/index.js +1 -145
- package/src/rules/no-algorithm-none/index.js +1 -184
- package/src/rules/no-decode-without-verify/index.js +1 -117
- package/src/rules/no-hardcoded-secret/index.js +1 -150
- package/src/rules/no-sensitive-payload/index.js +1 -97
- package/src/rules/no-timestamp-manipulation/index.js +1 -91
- package/src/rules/no-weak-secret/index.js +1 -146
- package/src/rules/require-algorithm-whitelist/index.js +1 -104
- package/src/rules/require-audience-validation/index.js +1 -93
- package/src/rules/require-expiration/index.js +1 -120
- package/src/rules/require-issued-at/index.js +1 -92
- package/src/rules/require-issuer-validation/index.js +1 -93
- package/src/rules/require-max-age/index.js +1 -86
- package/src/types/index.js +1 -2
- package/src/utils/index.js +1 -165
|
@@ -1,150 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noHardcodedSecret = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const utils_1 = require("../../utils");
|
|
6
|
-
exports.noHardcodedSecret = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-hardcoded-secret',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-hardcoded-secret.md',
|
|
12
|
-
description: 'Disallow hardcoded secrets in JWT sign/verify operations',
|
|
13
|
-
cwe: 'CWE-798',
|
|
14
|
-
cvss: 9.8,
|
|
15
|
-
},
|
|
16
|
-
fixable: undefined,
|
|
17
|
-
hasSuggestions: false,
|
|
18
|
-
messages: {
|
|
19
|
-
hardcodedSecret: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
21
|
-
issueName: 'Hardcoded JWT Secret',
|
|
22
|
-
cwe: 'CWE-798',
|
|
23
|
-
description: 'Secret key hardcoded in source code can be extracted from repositories',
|
|
24
|
-
severity: 'HIGH',
|
|
25
|
-
fix: 'Use process.env.JWT_SECRET or secure secret management',
|
|
26
|
-
documentationLink: 'https://cwe.mitre.org/data/definitions/798.html',
|
|
27
|
-
}),
|
|
28
|
-
useEnvVariable: (0, eslint_devkit_1.formatLLMMessage)({
|
|
29
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
30
|
-
issueName: 'Use Environment Variable',
|
|
31
|
-
description: 'Replace hardcoded secret with environment variable',
|
|
32
|
-
severity: 'LOW',
|
|
33
|
-
fix: 'process.env.JWT_SECRET',
|
|
34
|
-
documentationLink: 'https://12factor.net/config',
|
|
35
|
-
}),
|
|
36
|
-
},
|
|
37
|
-
schema: [
|
|
38
|
-
{
|
|
39
|
-
type: 'object',
|
|
40
|
-
properties: {
|
|
41
|
-
envPatterns: {
|
|
42
|
-
type: 'array',
|
|
43
|
-
items: { type: 'string' },
|
|
44
|
-
default: [],
|
|
45
|
-
description: 'Patterns that indicate safe environment variable usage',
|
|
46
|
-
},
|
|
47
|
-
trustedSanitizers: {
|
|
48
|
-
type: 'array',
|
|
49
|
-
items: { type: 'string' },
|
|
50
|
-
default: [],
|
|
51
|
-
},
|
|
52
|
-
trustedAnnotations: {
|
|
53
|
-
type: 'array',
|
|
54
|
-
items: { type: 'string' },
|
|
55
|
-
default: [],
|
|
56
|
-
},
|
|
57
|
-
strictMode: {
|
|
58
|
-
type: 'boolean',
|
|
59
|
-
default: false,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
additionalProperties: false,
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
defaultOptions: [
|
|
67
|
-
{
|
|
68
|
-
envPatterns: [],
|
|
69
|
-
trustedSanitizers: [],
|
|
70
|
-
trustedAnnotations: [],
|
|
71
|
-
strictMode: false,
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
create(context) {
|
|
75
|
-
const isHardcodedString = (node) => {
|
|
76
|
-
if (node.type === 'Literal' && typeof node.value === 'string') {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
if (node.type === 'TemplateLiteral' &&
|
|
80
|
-
node.expressions.length === 0) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
};
|
|
85
|
-
const resolveConstLiteralValue = (node) => {
|
|
86
|
-
if (node.type !== 'Identifier')
|
|
87
|
-
return null;
|
|
88
|
-
const scope = context.sourceCode.getScope?.(node) ?? null;
|
|
89
|
-
if (!scope)
|
|
90
|
-
return null;
|
|
91
|
-
const variable = scope.references.find((r) => r.identifier === node)?.resolved;
|
|
92
|
-
const def = variable?.defs[0];
|
|
93
|
-
if (!def || def.type !== 'Variable')
|
|
94
|
-
return null;
|
|
95
|
-
const decl = def.parent;
|
|
96
|
-
if (decl?.type !== 'VariableDeclaration' || decl.kind !== 'const')
|
|
97
|
-
return null;
|
|
98
|
-
return def.node.init ?? null;
|
|
99
|
-
};
|
|
100
|
-
const isSafeKeySource = (node) => {
|
|
101
|
-
if ((0, utils_1.isEnvVariable)(node)) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
if (node.type === 'CallExpression') {
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
if (node.type === 'AwaitExpression') {
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
if (node.type === 'Identifier') {
|
|
111
|
-
const init = resolveConstLiteralValue(node);
|
|
112
|
-
if (init && isHardcodedString(init)) {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
if (node.type === 'MemberExpression' && !isHardcodedString(node)) {
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
return false;
|
|
121
|
-
};
|
|
122
|
-
const isHardcodedStringOrResolvedConst = (node) => {
|
|
123
|
-
if (isHardcodedString(node))
|
|
124
|
-
return true;
|
|
125
|
-
const init = resolveConstLiteralValue(node);
|
|
126
|
-
return init ? isHardcodedString(init) : false;
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
CallExpression(node) {
|
|
130
|
-
if (!(0, utils_1.isSignOperation)(node) && !(0, utils_1.isVerifyOperation)(node)) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (node.arguments.length < 2) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const secretArg = node.arguments[1];
|
|
137
|
-
if (isSafeKeySource(secretArg)) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (isHardcodedStringOrResolvedConst(secretArg)) {
|
|
141
|
-
context.report({
|
|
142
|
-
node: secretArg,
|
|
143
|
-
messageId: 'hardcodedSecret',
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
exports.default = exports.noHardcodedSecret;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedSecret=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noHardcodedSecret=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-secret",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-hardcoded-secret.md",description:"Disallow hardcoded secrets in JWT sign/verify operations",cwe:"CWE-798",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{hardcodedSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded JWT Secret",cwe:"CWE-798",description:"Secret key hardcoded in source code can be extracted from repositories",severity:"HIGH",fix:"Use process.env.JWT_SECRET or secure secret management",documentationLink:"https://cwe.mitre.org/data/definitions/798.html"}),useEnvVariable:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Environment Variable",description:"Replace hardcoded secret with environment variable",severity:"LOW",fix:"process.env.JWT_SECRET",documentationLink:"https://12factor.net/config"})},schema:[{type:"object",properties:{envPatterns:{type:"array",items:{type:"string"},default:[],description:"Patterns that indicate safe environment variable usage"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{envPatterns:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const isHardcodedString=node=>{if(node.type==="Literal"&&typeof node.value==="string"){return true}if(node.type==="TemplateLiteral"&&node.expressions.length===0){return true}return false};const resolveConstLiteralValue=node=>{if(node.type!=="Identifier")return null;const scope=context.sourceCode.getScope?.(node)??null;if(!scope)return null;const variable=scope.references.find(r=>r.identifier===node)?.resolved;const def=variable?.defs[0];if(!def||def.type!=="Variable")return null;const decl=def.parent;if(decl?.type!=="VariableDeclaration"||decl.kind!=="const")return null;return def.node.init??null};const isSafeKeySource=node=>{if((0,utils_1.isEnvVariable)(node)){return true}if(node.type==="CallExpression"){return true}if(node.type==="AwaitExpression"){return true}if(node.type==="Identifier"){const init=resolveConstLiteralValue(node);if(init&&isHardcodedString(init)){return false}return true}if(node.type==="MemberExpression"&&!isHardcodedString(node)){return true}return false};const isHardcodedStringOrResolvedConst=node=>{if(isHardcodedString(node))return true;const init=resolveConstLiteralValue(node);return init?isHardcodedString(init):false};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)&&!(0,utils_1.isVerifyOperation)(node)){return}if(node.arguments.length<2){return}const secretArg=node.arguments[1];if(isSafeKeySource(secretArg)){return}if(isHardcodedStringOrResolvedConst(secretArg)){context.report({node:secretArg,messageId:"hardcodedSecret"})}}}}});exports.default=exports.noHardcodedSecret;
|
|
@@ -1,97 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noSensitivePayload = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const utils_1 = require("../../utils");
|
|
6
|
-
exports.noSensitivePayload = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-sensitive-payload',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-sensitive-payload.md',
|
|
12
|
-
description: 'Prevent storing sensitive data in JWT payload which is only base64-encoded',
|
|
13
|
-
cwe: 'CWE-359',
|
|
14
|
-
cvss: 5,
|
|
15
|
-
},
|
|
16
|
-
fixable: undefined,
|
|
17
|
-
hasSuggestions: false,
|
|
18
|
-
messages: {
|
|
19
|
-
sensitivePayloadField: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
21
|
-
issueName: 'Sensitive Data in JWT Payload',
|
|
22
|
-
cwe: 'CWE-359',
|
|
23
|
-
description: 'JWT payloads are not encrypted - sensitive data like "{{fieldName}}" can be read by anyone',
|
|
24
|
-
severity: 'MEDIUM',
|
|
25
|
-
fix: 'Store sensitive data server-side, reference by ID in token',
|
|
26
|
-
documentationLink: 'https://tools.ietf.org/html/rfc8725',
|
|
27
|
-
}),
|
|
28
|
-
},
|
|
29
|
-
schema: [
|
|
30
|
-
{
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
additionalSensitiveFields: {
|
|
34
|
-
type: 'array',
|
|
35
|
-
items: { type: 'string' },
|
|
36
|
-
default: [],
|
|
37
|
-
description: 'Additional field names to flag as sensitive',
|
|
38
|
-
},
|
|
39
|
-
trustedSanitizers: {
|
|
40
|
-
type: 'array',
|
|
41
|
-
items: { type: 'string' },
|
|
42
|
-
default: [],
|
|
43
|
-
},
|
|
44
|
-
trustedAnnotations: {
|
|
45
|
-
type: 'array',
|
|
46
|
-
items: { type: 'string' },
|
|
47
|
-
default: [],
|
|
48
|
-
},
|
|
49
|
-
strictMode: {
|
|
50
|
-
type: 'boolean',
|
|
51
|
-
default: false,
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
additionalProperties: false,
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
defaultOptions: [
|
|
59
|
-
{
|
|
60
|
-
additionalSensitiveFields: [],
|
|
61
|
-
trustedSanitizers: [],
|
|
62
|
-
trustedAnnotations: [],
|
|
63
|
-
strictMode: false,
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
create(context) {
|
|
67
|
-
const options = context.options[0] ?? {};
|
|
68
|
-
const additionalFields = options.additionalSensitiveFields ?? [];
|
|
69
|
-
const allSensitiveFields = new Set([...utils_1.SENSITIVE_PAYLOAD_FIELDS, ...additionalFields]);
|
|
70
|
-
return {
|
|
71
|
-
CallExpression(node) {
|
|
72
|
-
if (!(0, utils_1.isSignOperation)(node)) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (node.arguments.length < 1) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const payloadArg = node.arguments[0];
|
|
79
|
-
if (payloadArg.type === 'ObjectExpression') {
|
|
80
|
-
for (const prop of payloadArg.properties) {
|
|
81
|
-
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|
|
82
|
-
const fieldName = prop.key.name.toLowerCase();
|
|
83
|
-
if (allSensitiveFields.has(fieldName)) {
|
|
84
|
-
context.report({
|
|
85
|
-
node: prop,
|
|
86
|
-
messageId: 'sensitivePayloadField',
|
|
87
|
-
data: { fieldName: prop.key.name },
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
exports.default = exports.noSensitivePayload;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitivePayload=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noSensitivePayload=(0,eslint_devkit_1.createRule)({name:"no-sensitive-payload",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-sensitive-payload.md",description:"Prevent storing sensitive data in JWT payload which is only base64-encoded",cwe:"CWE-359",cvss:5},fixable:void 0,hasSuggestions:false,messages:{sensitivePayloadField:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in JWT Payload",cwe:"CWE-359",description:'JWT payloads are not encrypted - sensitive data like "{{fieldName}}" can be read by anyone',severity:"MEDIUM",fix:"Store sensitive data server-side, reference by ID in token",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{additionalSensitiveFields:{type:"array",items:{type:"string"},default:[],description:"Additional field names to flag as sensitive"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveFields:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const additionalFields=options.additionalSensitiveFields??[];const allSensitiveFields=new Set([...utils_1.SENSITIVE_PAYLOAD_FIELDS,...additionalFields]);return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)){return}if(node.arguments.length<1){return}const payloadArg=node.arguments[0];if(payloadArg.type==="ObjectExpression"){for(const prop of payloadArg.properties){if(prop.type==="Property"&&prop.key.type==="Identifier"){const fieldName=prop.key.name.toLowerCase();if(allSensitiveFields.has(fieldName)){context.report({node:prop,messageId:"sensitivePayloadField",data:{fieldName:prop.key.name}})}}}}}}}});exports.default=exports.noSensitivePayload;
|
|
@@ -1,91 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noTimestampManipulation = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const utils_1 = require("../../utils");
|
|
6
|
-
exports.noTimestampManipulation = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-timestamp-manipulation',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-timestamp-manipulation.md',
|
|
12
|
-
description: 'Prevent disabling automatic timestamp generation which enables replay attacks',
|
|
13
|
-
cwe: 'CWE-294',
|
|
14
|
-
cvss: 7.5,
|
|
15
|
-
},
|
|
16
|
-
fixable: undefined,
|
|
17
|
-
hasSuggestions: false,
|
|
18
|
-
messages: {
|
|
19
|
-
timestampDisabled: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
21
|
-
issueName: 'Timestamp Generation Disabled',
|
|
22
|
-
cwe: 'CWE-294',
|
|
23
|
-
description: 'noTimestamp:true disables iat claim, enabling replay attacks',
|
|
24
|
-
severity: 'HIGH',
|
|
25
|
-
fix: 'Remove noTimestamp option or set to false',
|
|
26
|
-
documentationLink: 'https://securitypattern.com/post/jwt-back-to-the-future',
|
|
27
|
-
}),
|
|
28
|
-
noTimestampTrue: (0, eslint_devkit_1.formatLLMMessage)({
|
|
29
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
30
|
-
issueName: 'Back to the Future Attack Vector',
|
|
31
|
-
cwe: 'CWE-294',
|
|
32
|
-
description: 'Disabling timestamps allows attackers to forge tokens valid in the future',
|
|
33
|
-
severity: 'HIGH',
|
|
34
|
-
fix: 'Always include iat claim for freshness validation',
|
|
35
|
-
documentationLink: 'https://securitypattern.com/post/jwt-back-to-the-future',
|
|
36
|
-
}),
|
|
37
|
-
},
|
|
38
|
-
schema: [
|
|
39
|
-
{
|
|
40
|
-
type: 'object',
|
|
41
|
-
properties: {
|
|
42
|
-
trustedSanitizers: {
|
|
43
|
-
type: 'array',
|
|
44
|
-
items: { type: 'string' },
|
|
45
|
-
default: [],
|
|
46
|
-
},
|
|
47
|
-
trustedAnnotations: {
|
|
48
|
-
type: 'array',
|
|
49
|
-
items: { type: 'string' },
|
|
50
|
-
default: [],
|
|
51
|
-
},
|
|
52
|
-
strictMode: {
|
|
53
|
-
type: 'boolean',
|
|
54
|
-
default: false,
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
additionalProperties: false,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
defaultOptions: [
|
|
62
|
-
{
|
|
63
|
-
trustedSanitizers: [],
|
|
64
|
-
trustedAnnotations: [],
|
|
65
|
-
strictMode: false,
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
create(context) {
|
|
69
|
-
return {
|
|
70
|
-
CallExpression(node) {
|
|
71
|
-
if (!(0, utils_1.isSignOperation)(node)) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
|
|
75
|
-
if (!optionsArg) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if ((0, utils_1.hasOption)(optionsArg, 'noTimestamp')) {
|
|
79
|
-
const value = (0, utils_1.getOptionValue)(optionsArg, 'noTimestamp');
|
|
80
|
-
if (value?.type === 'Literal' && value.value === true) {
|
|
81
|
-
context.report({
|
|
82
|
-
node: value,
|
|
83
|
-
messageId: 'noTimestampTrue',
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
exports.default = exports.noTimestampManipulation;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimestampManipulation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noTimestampManipulation=(0,eslint_devkit_1.createRule)({name:"no-timestamp-manipulation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-timestamp-manipulation.md",description:"Prevent disabling automatic timestamp generation which enables replay attacks",cwe:"CWE-294",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{timestampDisabled:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timestamp Generation Disabled",cwe:"CWE-294",description:"noTimestamp:true disables iat claim, enabling replay attacks",severity:"HIGH",fix:"Remove noTimestamp option or set to false",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),noTimestampTrue:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Back to the Future Attack Vector",cwe:"CWE-294",description:"Disabling timestamps allows attackers to forge tokens valid in the future",severity:"HIGH",fix:"Always include iat claim for freshness validation",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"})},schema:[{type:"object",properties:{trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){return}if((0,utils_1.hasOption)(optionsArg,"noTimestamp")){const value=(0,utils_1.getOptionValue)(optionsArg,"noTimestamp");if(value?.type==="Literal"&&value.value===true){context.report({node:value,messageId:"noTimestampTrue"})}}}}}});exports.default=exports.noTimestampManipulation;
|
|
@@ -1,146 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noWeakSecret = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const utils_1 = require("../../utils");
|
|
6
|
-
const WEAK_SECRET_PATTERNS = [
|
|
7
|
-
/^secret$/i,
|
|
8
|
-
/^password$/i,
|
|
9
|
-
/^123456/,
|
|
10
|
-
/^test/i,
|
|
11
|
-
/^demo/i,
|
|
12
|
-
/^example/i,
|
|
13
|
-
/^changeme/i,
|
|
14
|
-
/^default/i,
|
|
15
|
-
];
|
|
16
|
-
exports.noWeakSecret = (0, eslint_devkit_1.createRule)({
|
|
17
|
-
name: 'no-weak-secret',
|
|
18
|
-
meta: {
|
|
19
|
-
type: 'problem',
|
|
20
|
-
docs: {
|
|
21
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-weak-secret.md',
|
|
22
|
-
description: 'Require strong secrets (256+ bits) for HMAC-based JWT signing',
|
|
23
|
-
cwe: 'CWE-326',
|
|
24
|
-
cvss: 5.9,
|
|
25
|
-
},
|
|
26
|
-
fixable: undefined,
|
|
27
|
-
hasSuggestions: false,
|
|
28
|
-
messages: {
|
|
29
|
-
weakSecret: (0, eslint_devkit_1.formatLLMMessage)({
|
|
30
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
31
|
-
issueName: 'Weak JWT Secret',
|
|
32
|
-
cwe: 'CWE-326',
|
|
33
|
-
description: 'JWT secret is too weak and can be brute-forced',
|
|
34
|
-
severity: 'HIGH',
|
|
35
|
-
fix: 'Use crypto.randomBytes(32).toString("hex") for secrets',
|
|
36
|
-
documentationLink: 'https://tools.ietf.org/html/rfc8725',
|
|
37
|
-
}),
|
|
38
|
-
shortSecret: (0, eslint_devkit_1.formatLLMMessage)({
|
|
39
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
40
|
-
issueName: 'Short JWT Secret',
|
|
41
|
-
cwe: 'CWE-326',
|
|
42
|
-
description: 'JWT secret must be at least 32 characters (256 bits) for HS256',
|
|
43
|
-
severity: 'HIGH',
|
|
44
|
-
fix: 'Generate a secret with: crypto.randomBytes(32).toString("hex")',
|
|
45
|
-
documentationLink: 'https://tools.ietf.org/html/rfc8725',
|
|
46
|
-
}),
|
|
47
|
-
useStrongSecret: (0, eslint_devkit_1.formatLLMMessage)({
|
|
48
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
49
|
-
issueName: 'Use Strong Secret',
|
|
50
|
-
description: 'Replace with cryptographically strong secret',
|
|
51
|
-
severity: 'LOW',
|
|
52
|
-
fix: 'Use process.env.JWT_SECRET with a 256-bit random value',
|
|
53
|
-
documentationLink: 'https://nodejs.org/api/crypto.html',
|
|
54
|
-
}),
|
|
55
|
-
},
|
|
56
|
-
schema: [
|
|
57
|
-
{
|
|
58
|
-
type: 'object',
|
|
59
|
-
properties: {
|
|
60
|
-
minSecretLength: {
|
|
61
|
-
type: 'integer',
|
|
62
|
-
default: 32,
|
|
63
|
-
minimum: 16,
|
|
64
|
-
description: 'Minimum secret length in characters',
|
|
65
|
-
},
|
|
66
|
-
trustedSanitizers: {
|
|
67
|
-
type: 'array',
|
|
68
|
-
items: { type: 'string' },
|
|
69
|
-
default: [],
|
|
70
|
-
},
|
|
71
|
-
trustedAnnotations: {
|
|
72
|
-
type: 'array',
|
|
73
|
-
items: { type: 'string' },
|
|
74
|
-
default: [],
|
|
75
|
-
},
|
|
76
|
-
strictMode: {
|
|
77
|
-
type: 'boolean',
|
|
78
|
-
default: false,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
additionalProperties: false,
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
defaultOptions: [
|
|
86
|
-
{
|
|
87
|
-
minSecretLength: 32,
|
|
88
|
-
trustedSanitizers: [],
|
|
89
|
-
trustedAnnotations: [],
|
|
90
|
-
strictMode: false,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
create(context) {
|
|
94
|
-
const options = context.options[0] ?? {};
|
|
95
|
-
const { minSecretLength = 32 } = options;
|
|
96
|
-
const isKnownWeakPattern = (value) => {
|
|
97
|
-
return WEAK_SECRET_PATTERNS.some((pattern) => pattern.test(value));
|
|
98
|
-
};
|
|
99
|
-
const checkSecret = (secretNode) => {
|
|
100
|
-
if ((0, utils_1.isEnvVariable)(secretNode)) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (secretNode.type === 'Literal' &&
|
|
104
|
-
typeof secretNode.value === 'string') {
|
|
105
|
-
const secretValue = secretNode.value;
|
|
106
|
-
if (isKnownWeakPattern(secretValue)) {
|
|
107
|
-
context.report({
|
|
108
|
-
node: secretNode,
|
|
109
|
-
messageId: 'weakSecret',
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (secretValue.length < minSecretLength) {
|
|
114
|
-
context.report({
|
|
115
|
-
node: secretNode,
|
|
116
|
-
messageId: 'shortSecret',
|
|
117
|
-
data: {
|
|
118
|
-
length: String(secretValue.length),
|
|
119
|
-
minLength: String(minSecretLength),
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (secretNode.type === 'TemplateLiteral' && secretNode.quasis.length === 1) {
|
|
125
|
-
const rawValue = secretNode.quasis[0].value.raw;
|
|
126
|
-
if (isKnownWeakPattern(rawValue) || rawValue.length < minSecretLength) {
|
|
127
|
-
context.report({
|
|
128
|
-
node: secretNode,
|
|
129
|
-
messageId: 'weakSecret',
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
return {
|
|
135
|
-
CallExpression(node) {
|
|
136
|
-
if (!(0, utils_1.isSignOperation)(node) && !(0, utils_1.isVerifyOperation)(node)) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (node.arguments.length >= 2) {
|
|
140
|
-
checkSecret(node.arguments[1]);
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
exports.default = exports.noWeakSecret;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakSecret=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const WEAK_SECRET_PATTERNS=[/^secret$/i,/^password$/i,/^123456/,/^test/i,/^demo/i,/^example/i,/^changeme/i,/^default/i];exports.noWeakSecret=(0,eslint_devkit_1.createRule)({name:"no-weak-secret",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-weak-secret.md",description:"Require strong secrets (256+ bits) for HMAC-based JWT signing",cwe:"CWE-326",cvss:5.9},fixable:void 0,hasSuggestions:false,messages:{weakSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak JWT Secret",cwe:"CWE-326",description:"JWT secret is too weak and can be brute-forced",severity:"HIGH",fix:'Use crypto.randomBytes(32).toString("hex") for secrets',documentationLink:"https://tools.ietf.org/html/rfc8725"}),shortSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Short JWT Secret",cwe:"CWE-326",description:"JWT secret must be at least 32 characters (256 bits) for HS256",severity:"HIGH",fix:'Generate a secret with: crypto.randomBytes(32).toString("hex")',documentationLink:"https://tools.ietf.org/html/rfc8725"}),useStrongSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Strong Secret",description:"Replace with cryptographically strong secret",severity:"LOW",fix:"Use process.env.JWT_SECRET with a 256-bit random value",documentationLink:"https://nodejs.org/api/crypto.html"})},schema:[{type:"object",properties:{minSecretLength:{type:"integer",default:32,minimum:16,description:"Minimum secret length in characters"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{minSecretLength:32,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const{minSecretLength=32}=options;const isKnownWeakPattern=value=>{return WEAK_SECRET_PATTERNS.some(pattern=>pattern.test(value))};const checkSecret=secretNode=>{if((0,utils_1.isEnvVariable)(secretNode)){return}if(secretNode.type==="Literal"&&typeof secretNode.value==="string"){const secretValue=secretNode.value;if(isKnownWeakPattern(secretValue)){context.report({node:secretNode,messageId:"weakSecret"});return}if(secretValue.length<minSecretLength){context.report({node:secretNode,messageId:"shortSecret",data:{length:String(secretValue.length),minLength:String(minSecretLength)}})}}if(secretNode.type==="TemplateLiteral"&&secretNode.quasis.length===1){const rawValue=secretNode.quasis[0].value.raw;if(isKnownWeakPattern(rawValue)||rawValue.length<minSecretLength){context.report({node:secretNode,messageId:"weakSecret"})}}};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)&&!(0,utils_1.isVerifyOperation)(node)){return}if(node.arguments.length>=2){checkSecret(node.arguments[1])}}}}});exports.default=exports.noWeakSecret;
|
|
@@ -1,104 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.requireAlgorithmWhitelist = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const utils_1 = require("../../utils");
|
|
6
|
-
exports.requireAlgorithmWhitelist = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'require-algorithm-whitelist',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-algorithm-whitelist.md',
|
|
12
|
-
description: 'Require explicit algorithm specification in JWT verify operations',
|
|
13
|
-
cwe: 'CWE-757',
|
|
14
|
-
cvss: 7.5,
|
|
15
|
-
},
|
|
16
|
-
fixable: undefined,
|
|
17
|
-
hasSuggestions: false,
|
|
18
|
-
messages: {
|
|
19
|
-
missingAlgorithmWhitelist: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
21
|
-
issueName: 'Missing Algorithm Whitelist',
|
|
22
|
-
cwe: 'CWE-757',
|
|
23
|
-
description: 'JWT verification without explicit algorithms trusts the token header',
|
|
24
|
-
severity: 'HIGH',
|
|
25
|
-
fix: 'Add algorithms: ["RS256"] or algorithms: ["ES256"] to options',
|
|
26
|
-
documentationLink: 'https://tools.ietf.org/html/rfc8725',
|
|
27
|
-
}),
|
|
28
|
-
addAlgorithmWhitelist: (0, eslint_devkit_1.formatLLMMessage)({
|
|
29
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
30
|
-
issueName: 'Add Algorithm Whitelist',
|
|
31
|
-
description: 'Specify allowed algorithms explicitly',
|
|
32
|
-
severity: 'LOW',
|
|
33
|
-
fix: 'jwt.verify(token, key, { algorithms: ["RS256"] })',
|
|
34
|
-
documentationLink: 'https://tools.ietf.org/html/rfc8725',
|
|
35
|
-
}),
|
|
36
|
-
},
|
|
37
|
-
schema: [
|
|
38
|
-
{
|
|
39
|
-
type: 'object',
|
|
40
|
-
properties: {
|
|
41
|
-
recommendedAlgorithms: {
|
|
42
|
-
type: 'array',
|
|
43
|
-
items: { type: 'string' },
|
|
44
|
-
default: ['RS256', 'ES256'],
|
|
45
|
-
description: 'Algorithms to suggest in auto-fix',
|
|
46
|
-
},
|
|
47
|
-
trustedSanitizers: {
|
|
48
|
-
type: 'array',
|
|
49
|
-
items: { type: 'string' },
|
|
50
|
-
default: [],
|
|
51
|
-
},
|
|
52
|
-
trustedAnnotations: {
|
|
53
|
-
type: 'array',
|
|
54
|
-
items: { type: 'string' },
|
|
55
|
-
default: [],
|
|
56
|
-
},
|
|
57
|
-
strictMode: {
|
|
58
|
-
type: 'boolean',
|
|
59
|
-
default: false,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
additionalProperties: false,
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
defaultOptions: [
|
|
67
|
-
{
|
|
68
|
-
recommendedAlgorithms: ['RS256', 'ES256'],
|
|
69
|
-
trustedSanitizers: [],
|
|
70
|
-
trustedAnnotations: [],
|
|
71
|
-
strictMode: false,
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
create(context) {
|
|
75
|
-
return {
|
|
76
|
-
CallExpression(node) {
|
|
77
|
-
if (!(0, utils_1.isVerifyOperation)(node)) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (node.arguments.length < 2) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
|
|
84
|
-
if (!optionsArg) {
|
|
85
|
-
context.report({
|
|
86
|
-
node,
|
|
87
|
-
messageId: 'missingAlgorithmWhitelist',
|
|
88
|
-
});
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const hasAlgorithms = (0, utils_1.hasOption)(optionsArg, 'algorithms') ||
|
|
92
|
-
(0, utils_1.hasOption)(optionsArg, 'algorithm') ||
|
|
93
|
-
(0, utils_1.hasOption)(optionsArg, 'alg');
|
|
94
|
-
if (!hasAlgorithms) {
|
|
95
|
-
context.report({
|
|
96
|
-
node: optionsArg,
|
|
97
|
-
messageId: 'missingAlgorithmWhitelist',
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
exports.default = exports.requireAlgorithmWhitelist;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAlgorithmWhitelist=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireAlgorithmWhitelist=(0,eslint_devkit_1.createRule)({name:"require-algorithm-whitelist",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-algorithm-whitelist.md",description:"Require explicit algorithm specification in JWT verify operations",cwe:"CWE-757",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{missingAlgorithmWhitelist:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Algorithm Whitelist",cwe:"CWE-757",description:"JWT verification without explicit algorithms trusts the token header",severity:"HIGH",fix:'Add algorithms: ["RS256"] or algorithms: ["ES256"] to options',documentationLink:"https://tools.ietf.org/html/rfc8725"}),addAlgorithmWhitelist:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Algorithm Whitelist",description:"Specify allowed algorithms explicitly",severity:"LOW",fix:'jwt.verify(token, key, { algorithms: ["RS256"] })',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{recommendedAlgorithms:{type:"array",items:{type:"string"},default:["RS256","ES256"],description:"Algorithms to suggest in auto-fix"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{recommendedAlgorithms:["RS256","ES256"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node)){return}if(node.arguments.length<2){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){context.report({node,messageId:"missingAlgorithmWhitelist"});return}const hasAlgorithms=(0,utils_1.hasOption)(optionsArg,"algorithms")||(0,utils_1.hasOption)(optionsArg,"algorithm")||(0,utils_1.hasOption)(optionsArg,"alg");if(!hasAlgorithms){context.report({node:optionsArg,messageId:"missingAlgorithmWhitelist"})}}}}});exports.default=exports.requireAlgorithmWhitelist;
|