@appland/scanner 1.69.1 → 1.70.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [@appland/scanner-v1.70.0](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.69.1...@appland/scanner-v1.70.0) (2022-08-31)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Don't attempt to destructure an undefined value ([b46e358](https://github.com/applandinc/appmap-js/commit/b46e358cf0fd0cc56a7f465268f87f219ab13c55))
7
+
8
+
9
+ ### Features
10
+
11
+ * Add scan for presence of JWT signature verification ([a2b382b](https://github.com/applandinc/appmap-js/commit/a2b382bd571cfbc0fcdfa389ad382536f85eb671))
12
+ * Add scanner for usage of JWT with the `none` algorithm ([025ac89](https://github.com/applandinc/appmap-js/commit/025ac89f0538d5b4bfed7f36e3d09788f2a38076))
13
+
1
14
  # [@appland/scanner-v1.69.1](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.69.0...@appland/scanner-v1.69.1) (2022-08-29)
2
15
 
3
16
 
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
7
+ function getHeader(jwt) {
8
+ try {
9
+ const [header] = jwt.split('.');
10
+ const decodedHeader = Buffer.from(header, 'base64').toString('utf-8');
11
+ return JSON.parse(decodedHeader);
12
+ }
13
+ catch (_a) {
14
+ // the JWT is malformed
15
+ return undefined;
16
+ }
17
+ }
18
+ class JwtAlgoritmNoneLogic {
19
+ matcher(event) {
20
+ if (!event.returnValue)
21
+ return;
22
+ const matches = new Array();
23
+ const { value: jwt } = event.returnValue;
24
+ const header = getHeader(jwt);
25
+ if ((header === null || header === void 0 ? void 0 : header.alg) === 'none') {
26
+ matches.push({ event, message: 'Encoded JWT using the `none` algorithm' });
27
+ }
28
+ return matches;
29
+ }
30
+ where(event) {
31
+ return event.labels.has('jwt.encode');
32
+ }
33
+ }
34
+ class JwtAlgoritmNone {
35
+ constructor() {
36
+ this.id = 'jwt-algorithm-none';
37
+ this.title = "JWT 'none' algorithm";
38
+ this.impactDomain = 'Security';
39
+ this.enumerateScope = true;
40
+ this.description = (0, parseRuleDescription_1.default)('jwtAlgorithmNone');
41
+ this.url = 'https://appland.com/docs/analysis/rules-reference.html#jwt-algorithm-none';
42
+ }
43
+ build() {
44
+ return new JwtAlgoritmNoneLogic();
45
+ }
46
+ }
47
+ exports.default = new JwtAlgoritmNone();
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Labels = void 0;
7
+ const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
8
+ const models_1 = require("@appland/models");
9
+ var Labels;
10
+ (function (Labels) {
11
+ Labels["SignatureVerify"] = "jwt.signature.verify";
12
+ Labels["JwtDecode"] = "jwt.decode";
13
+ })(Labels = exports.Labels || (exports.Labels = {}));
14
+ // Attempt to identify and return a JWT from an array of parameters
15
+ function findJwt(parameters) {
16
+ if (!parameters)
17
+ return;
18
+ for (const param of parameters) {
19
+ const tokens = param.value.split('.');
20
+ if (tokens.length !== 3)
21
+ return;
22
+ const [header, payload, signature] = tokens;
23
+ return { header, payload, signature };
24
+ }
25
+ }
26
+ // Check if `obj` matches the JWT by value or by reference (receiverId)
27
+ function matchJwt(obj, jwt, receiverId) {
28
+ const byValue = jwt !== undefined && obj.value.startsWith(`${jwt.header}.${jwt.payload}`);
29
+ const byReference = receiverId !== undefined && receiverId === obj.object_id;
30
+ return byValue || byReference;
31
+ }
32
+ class JwtUnverifiedSignatureLogic {
33
+ matcher(event) {
34
+ var _a, _b, _c;
35
+ if (event.labels.has(Labels.SignatureVerify)) {
36
+ // This method is marked both as decode and signature verify. It is compliant.
37
+ return;
38
+ }
39
+ let verified = false;
40
+ let receiverId;
41
+ const jwt = findJwt(event.parameters);
42
+ const matches = new Array();
43
+ // Don't track the receiver if it's static. We'll find references of the decoded JWT passed by
44
+ // function parameter instead.
45
+ if (!event.isStatic) {
46
+ receiverId = (_a = event.receiver) === null || _a === void 0 ? void 0 : _a.object_id;
47
+ }
48
+ for (const { event: child } of new models_1.EventNavigator(event).following()) {
49
+ if (!child.labels.has(Labels.SignatureVerify)) {
50
+ continue;
51
+ }
52
+ const matchesReceiver = receiverId !== undefined && receiverId === ((_b = child.receiver) === null || _b === void 0 ? void 0 : _b.object_id);
53
+ const matchesParameter = (_c = child.parameters) === null || _c === void 0 ? void 0 : _c.find((param) => matchJwt(param, jwt, receiverId));
54
+ if (matchesReceiver || matchesParameter) {
55
+ verified = true;
56
+ break;
57
+ }
58
+ }
59
+ if (!verified) {
60
+ matches.push({
61
+ event,
62
+ message: 'JWT signature is not validated',
63
+ });
64
+ }
65
+ return matches;
66
+ }
67
+ where(event) {
68
+ return event.labels.has('jwt.decode');
69
+ }
70
+ }
71
+ class JwtUnverifiedSignature {
72
+ constructor() {
73
+ this.id = 'jwt-unverified-signature';
74
+ this.title = 'Unverified signature';
75
+ this.impactDomain = 'Security';
76
+ this.enumerateScope = true;
77
+ this.description = (0, parseRuleDescription_1.default)('jwtUnverifiedSignature');
78
+ this.url = 'https://appland.com/docs/analysis/rules-reference.html#jwt-unverified-signature';
79
+ }
80
+ build() {
81
+ return new JwtUnverifiedSignatureLogic();
82
+ }
83
+ }
84
+ exports.default = new JwtUnverifiedSignature();
@@ -0,0 +1,25 @@
1
+ ---
2
+ rule: jwt-algorithm-none
3
+ name: Jwt algorithm none
4
+ title: JWT 'none' algorithm
5
+ impactDomain: Security
6
+ ---
7
+
8
+ Finds usage of unsecured JWTs which use the `none` algorithm. When declaring this algorithm, there
9
+ is no signature contained within the token that may be cryptographically verified. As a result, the
10
+ data encoded within the token may be easily forged.
11
+
12
+ ### Rule logic
13
+
14
+ Any function which encodes a new JWT will have its return value checked for presence of the `none`
15
+ algorithm within the token header.
16
+
17
+ ### Options
18
+
19
+ None
20
+
21
+ ### Examples
22
+
23
+ ```yaml
24
+ - rule: jwt-algorithm-none
25
+ ```
@@ -0,0 +1,24 @@
1
+ ---
2
+ rule: jwt-unverified-signature
3
+ name: Jwt unverified signature
4
+ title: Unverified signature
5
+ impactDomain: Security
6
+ ---
7
+
8
+ Finds cases where a JWT is decoded but the signature is never verified. Without proper signature
9
+ verification, the service will unknowingly accept arbitrary token payloads from any origin.
10
+
11
+ ### Rule logic
12
+
13
+ Following a function call to decode a JWT, a subsequent function call to verify the token signature
14
+ is expected.
15
+
16
+ ### Options
17
+
18
+ None
19
+
20
+ ### Examples
21
+
22
+ ```yaml
23
+ - rule: jwt-unverified-signature
24
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appland/scanner",
3
- "version": "1.69.1",
3
+ "version": "1.70.0",
4
4
  "description": "",
5
5
  "bin": "built/cli.js",
6
6
  "files": [
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@appland/client": "^1.3.0",
59
- "@appland/models": "^1.16.1",
59
+ "@appland/models": "^1.18.1",
60
60
  "@appland/openapi": "1.0.2",
61
61
  "@appland/sql-parser": "^1.5.0",
62
62
  "@types/cli-progress": "^3.9.2",