permission-gap-detector 0.0.0-stage → 1.0.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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +235 -2
- package/bin/cli.js +40 -0
- package/examples/basic.js +3 -0
- package/examples/clean-routes.json +17 -0
- package/examples/routes.json +31 -0
- package/package.json +43 -4
- package/src/auditRoutes.js +25 -0
- package/src/checkDuplicates.js +26 -0
- package/src/checkPermissions.js +38 -0
- package/src/generateReport.js +21 -0
- package/src/index.js +15 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
- Reject control characters in route paths before rendering reports.
|
|
6
|
+
- Accept UTF-8 BOM JSON files and literal filenames after `--`.
|
|
7
|
+
- Add command-specific help with documented exit codes.
|
|
8
|
+
- Expand regression coverage to 27 tests and include a clean route example.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## 1.0.1
|
|
12
|
+
|
|
13
|
+
- Keep the distribution focused on source code, usage examples, and package documentation.
|
|
14
|
+
- Simplify the README and published file list.
|
|
15
|
+
|
|
16
|
+
## 1.0.0
|
|
17
|
+
|
|
18
|
+
- Add missing and invalid permission checks.
|
|
19
|
+
- Add duplicate and conflicting policy detection.
|
|
20
|
+
- Add summary counts and declaration coverage.
|
|
21
|
+
- Add CommonJS API and CLI with JSON output and exit codes.
|
|
22
|
+
- Add 20 automated tests and usage examples.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya030207
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,236 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Permission Gap Detector
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small, dependency-free CommonJS Node.js package that checks **declared API route permissions**.
|
|
4
|
+
|
|
5
|
+
## Problem statement
|
|
6
|
+
|
|
7
|
+
While building an Express REST API, a developer can forget to specify who should access a route. A route list makes these decisions visible. This package finds missing or inconsistent declarations so they can be reviewed.
|
|
8
|
+
|
|
9
|
+
It does not inspect Express middleware or prove that an application is secure.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Detect missing permissions and unsupported permission values.
|
|
14
|
+
- Support `public`, `authenticated`, and `admin` policies.
|
|
15
|
+
- Detect repeated method/path pairs and conflicting valid policies.
|
|
16
|
+
- Return structured findings and permission declaration coverage.
|
|
17
|
+
- Read route lists from JSON using a simple CLI.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Requires Node.js 20 or newer and npm. No external dependencies.
|
|
22
|
+
|
|
23
|
+
Install from npm:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install permission-gap-detector
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Alternatively, install directly from GitHub:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install github:Aditya030207/npm-package-project
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or run `npm pack` in this repository and install the resulting archive in another project:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install /absolute/path/to/permission-gap-detector-1.0.2.tgz
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Basic usage
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const { auditRoutes } = require('permission-gap-detector');
|
|
45
|
+
|
|
46
|
+
const routes = [
|
|
47
|
+
{ method: 'GET', path: '/products', access: 'public' },
|
|
48
|
+
{ method: 'DELETE', path: '/products/:id' }
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const result = auditRoutes(routes);
|
|
52
|
+
console.log(result.summary);
|
|
53
|
+
console.log(result.findings.missingPermissions);
|
|
54
|
+
// [{ index: 1, method: 'DELETE', path: '/products/:id' }]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Maintain this route list alongside your Express routes. The package does not automatically discover routes.
|
|
58
|
+
|
|
59
|
+
## Route format and validation
|
|
60
|
+
|
|
61
|
+
Input is an array of ordinary route objects. Each object needs:
|
|
62
|
+
|
|
63
|
+
| Field | Accepted values |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `method` | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, TRACE; case-insensitive |
|
|
66
|
+
| `path` | A string starting with `/`, without whitespace or control characters |
|
|
67
|
+
| `access` | Exactly `public`, `authenticated`, or `admin` |
|
|
68
|
+
|
|
69
|
+
`public` allows anyone; `authenticated` requires a logged-in user; `admin` requires administrator access. These are declarations, not enforcement.
|
|
70
|
+
|
|
71
|
+
Absent, undefined, null, empty, and whitespace-only access values are missing. Other unsupported values are invalid permissions. Invalid route objects, methods, and paths are reported as invalid routes and excluded from other checks. Additional fields are ignored. Methods are normalized to uppercase without changing the input. Paths are compared exactly: `/users`, `/Users`, `/users/`, and parameter names are distinct.
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
All four functions accept a route array and leave it unchanged. A non-array argument throws `TypeError`. Invalid entries inside an array are reported by `auditRoutes` instead of throwing.
|
|
76
|
+
|
|
77
|
+
### `auditRoutes(routes)`
|
|
78
|
+
|
|
79
|
+
Returns `{ summary, findings, hasIssues }`.
|
|
80
|
+
|
|
81
|
+
`summary` contains:
|
|
82
|
+
|
|
83
|
+
| Field | Meaning |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `totalRoutes` | All submitted definitions, including invalid entries |
|
|
86
|
+
| `validPermissions` | Structurally valid definitions with a supported access value |
|
|
87
|
+
| `missingPermissions` | Valid route structures with missing access |
|
|
88
|
+
| `invalidPermissions` | Valid route structures with unsupported access |
|
|
89
|
+
| `invalidRoutes` | Invalid route structures |
|
|
90
|
+
| `duplicateRoutes` | Extra definitions beyond the first in each repeated method/path group |
|
|
91
|
+
| `conflictingRoutes` | Method/path groups with at least two distinct supported policies |
|
|
92
|
+
| `coveragePercentage` | Valid permissions divided by total definitions, multiplied by 100, rounded to one decimal |
|
|
93
|
+
|
|
94
|
+
The four permission/validation categories sum to `totalRoutes`. An empty array has 0% coverage because no declarations were checked. Duplicates and conflicts overlap with these categories and must not be added to them. Three copies of one route count as two duplicates. A conflicting group counts once. Valid policies on duplicates still count toward declaration coverage: **100% coverage can coexist with conflicts and does not mean secure**.
|
|
95
|
+
|
|
96
|
+
`findings` contains five arrays:
|
|
97
|
+
|
|
98
|
+
- `missingPermissions`: `{ index, method, path }`.
|
|
99
|
+
- `invalidPermissions`: `{ index, method, path, access }`.
|
|
100
|
+
- `invalidRoutes`: `{ index, message }`.
|
|
101
|
+
- `duplicateRoutes`: `{ method, path, indices, extraDefinitions }`.
|
|
102
|
+
- `conflictingPermissions`: `{ method, path, indices, permissions }`.
|
|
103
|
+
|
|
104
|
+
Indices are zero-based positions in the original array. Conflict indices include all definitions in that group; `permissions` lists its distinct supported values. Missing and invalid policies do not themselves create conflicts. `hasIssues` is true when any findings array is nonempty.
|
|
105
|
+
|
|
106
|
+
### `findMissingPermissions(routes)`
|
|
107
|
+
|
|
108
|
+
Returns the `missingPermissions` findings array.
|
|
109
|
+
|
|
110
|
+
### `findDuplicateRoutes(routes)`
|
|
111
|
+
|
|
112
|
+
Returns the `duplicateRoutes` findings array, one object per repeated group.
|
|
113
|
+
|
|
114
|
+
### `findConflictingPermissions(routes)`
|
|
115
|
+
|
|
116
|
+
Returns the `conflictingPermissions` findings array, one object per conflicting group.
|
|
117
|
+
|
|
118
|
+
The three convenience functions return only their named findings. Use `auditRoutes` to see validation errors as well.
|
|
119
|
+
|
|
120
|
+
## CLI usage
|
|
121
|
+
|
|
122
|
+
Run directly from npm:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx permission-gap-detector check routes.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
After installation into a project:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx --no-install permission-gap check routes.json
|
|
132
|
+
npx --no-install permission-gap check routes.json --json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For a direct shell command, globally install the package with `npm install -g permission-gap-detector` then run `permission-gap check routes.json`. Both binary names point to the same CLI.
|
|
136
|
+
|
|
137
|
+
From this repository:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
node bin/cli.js check examples/routes.json
|
|
141
|
+
node bin/cli.js check examples/routes.json --json
|
|
142
|
+
node bin/cli.js --help
|
|
143
|
+
node bin/cli.js check --help
|
|
144
|
+
node bin/cli.js --version
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
UTF-8 files may begin with a byte order mark (BOM). Files must contain a JSON array, not an object wrapping the array. Paths are relative to the current working directory; quote paths containing spaces. Use `permission-gap check --json -- -routes.json` for a filename beginning with a dash; options must come before `--`. Exit codes: **0** = no findings, **1** = audit findings, **2** = invalid command, unreadable file, malformed JSON, or non-array input. Human-readable failures go to stderr. With `--json`, failures are `{ "error": "message" }` on stdout.
|
|
148
|
+
|
|
149
|
+
## Example output
|
|
150
|
+
|
|
151
|
+
Running the included six-definition example produces:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
==================================
|
|
155
|
+
PERMISSION GAP DETECTOR
|
|
156
|
+
==================================
|
|
157
|
+
|
|
158
|
+
Total Routes Checked: 6
|
|
159
|
+
Valid Permissions: 5
|
|
160
|
+
Missing Permissions: 1
|
|
161
|
+
Invalid Permissions: 0
|
|
162
|
+
Invalid Route Definitions: 0
|
|
163
|
+
Duplicate Routes (extra definitions): 2
|
|
164
|
+
Conflicting Policies (route groups): 1
|
|
165
|
+
|
|
166
|
+
Permission Coverage: 83.3%
|
|
167
|
+
|
|
168
|
+
Issues Found:
|
|
169
|
+
[WARNING] DELETE /products/:id: Missing permission declaration (index 2).
|
|
170
|
+
[WARNING] GET /products: Duplicate route definition (indices 0, 3).
|
|
171
|
+
[WARNING] GET /admin: Duplicate route definition (indices 4, 5).
|
|
172
|
+
[ERROR] GET /admin: Conflicting permissions: admin, public.
|
|
173
|
+
|
|
174
|
+
==================================
|
|
175
|
+
Audit Complete
|
|
176
|
+
==================================
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The example intentionally has issues and exits with code 1.
|
|
180
|
+
|
|
181
|
+
For a complete set of consistent declarations, run:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
node bin/cli.js check examples/clean-routes.json
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This example uses all three supported permissions, reports 100% declaration coverage with no findings, and exits with code 0. Compare it with `examples/routes.json` to see how missing and conflicting declarations change the result. Neither result verifies middleware enforcement.
|
|
188
|
+
|
|
189
|
+
## Development and testing
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git clone https://github.com/Aditya030207/npm-package-project.git
|
|
193
|
+
cd npm-package-project
|
|
194
|
+
npm ci
|
|
195
|
+
npm test
|
|
196
|
+
node examples/basic.js
|
|
197
|
+
npm pack --dry-run
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The 27 automated tests use `node:test` and strict assertions. They cover valid policies, input validation, missing permissions, duplicates, conflicts, counting, immutability, reports, and CLI behavior with real temporary files and child processes.
|
|
201
|
+
|
|
202
|
+
## Source structure
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
npm-package-project/
|
|
206
|
+
├── src/
|
|
207
|
+
│ ├── index.js # Four public exports
|
|
208
|
+
│ ├── auditRoutes.js # Combine checks and calculate summary
|
|
209
|
+
│ ├── checkPermissions.js # Validate input and access policies
|
|
210
|
+
│ ├── checkDuplicates.js # Group routes and find conflicts
|
|
211
|
+
│ └── generateReport.js # Format readable terminal output
|
|
212
|
+
├── bin/cli.js # Arguments, JSON file reading, exit codes
|
|
213
|
+
├── tests/package.test.js # API and CLI tests
|
|
214
|
+
├── examples/
|
|
215
|
+
│ ├── basic.js
|
|
216
|
+
│ ├── routes.json
|
|
217
|
+
│ └── clean-routes.json
|
|
218
|
+
├── package.json
|
|
219
|
+
├── package-lock.json
|
|
220
|
+
├── README.md
|
|
221
|
+
├── CHANGELOG.md
|
|
222
|
+
├── LICENSE
|
|
223
|
+
└── .gitignore
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Limitations
|
|
227
|
+
|
|
228
|
+
This audits only supplied route declarations. It does not scan source code, discover Express routes, execute middleware, validate tokens, enforce access rules, or assess application security. It does not infer route equivalence, inherited policies, Express mount paths, or wildcard matching. A public policy on a sensitive route is only detected as a conflict if another definition declares a different valid policy. Keep route declarations synchronized with the actual application.
|
|
229
|
+
|
|
230
|
+
## Contributing
|
|
231
|
+
|
|
232
|
+
Open an issue describing a problem or propose a small change. Add a meaningful test for changed behavior and run `npm test`. Keep the project simple and dependency-free.
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
MIT. See [LICENSE](LICENSE).
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const { auditRoutes } = require('../src');
|
|
4
|
+
const generateReport = require('../src/generateReport');
|
|
5
|
+
const { version } = require('../package.json');
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
// Arguments after -- are literal filenames, even when they begin with a dash.
|
|
8
|
+
const separator = args.indexOf('--');
|
|
9
|
+
const options = separator === -1 ? args : args.slice(0, separator);
|
|
10
|
+
const filenames = separator === -1 ? [] : args.slice(separator + 1);
|
|
11
|
+
const json = options.includes('--json');
|
|
12
|
+
const usage = 'Usage: permission-gap check <routes.json> [--json]\n permission-gap check [--json] -- <filename>\nOptions: --help, --version, --json\nExit codes: 0 = no findings, 1 = audit findings, 2 = input error';
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
if ((args.length === 1 && args[0] === '--help') ||
|
|
16
|
+
(args.length === 2 && args[0] === 'check' && args[1] === '--help')) {
|
|
17
|
+
console.log(usage);
|
|
18
|
+
} else if (args.length === 1 && args[0] === '--version') {
|
|
19
|
+
console.log(version);
|
|
20
|
+
} else {
|
|
21
|
+
const positional = [...options.filter(arg => arg !== '--json'), ...filenames];
|
|
22
|
+
const unknownOption = options.some(arg => arg.startsWith('-') && arg !== '--json');
|
|
23
|
+
if (positional.length !== 2 || positional[0] !== 'check' || unknownOption || options.filter(arg => arg === '--json').length > 1) {
|
|
24
|
+
throw new Error(usage);
|
|
25
|
+
}
|
|
26
|
+
let content;
|
|
27
|
+
try { content = fs.readFileSync(positional[1], 'utf8'); }
|
|
28
|
+
catch { throw new Error(`Cannot read file: ${positional[1]}`); }
|
|
29
|
+
let routes;
|
|
30
|
+
try { routes = JSON.parse(content.replace(/^\uFEFF/, '')); }
|
|
31
|
+
catch { throw new Error(`Invalid JSON in file: ${positional[1]}`); }
|
|
32
|
+
const result = auditRoutes(routes);
|
|
33
|
+
console.log(json ? JSON.stringify(result, null, 2) : generateReport(result));
|
|
34
|
+
process.exitCode = result.hasIssues ? 1 : 0;
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (json) console.log(JSON.stringify({ error: error.message }));
|
|
38
|
+
else console.error(`Error: ${error.message}`);
|
|
39
|
+
process.exitCode = 2;
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"method": "GET",
|
|
4
|
+
"path": "/products",
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"method": "POST",
|
|
9
|
+
"path": "/products",
|
|
10
|
+
"access": "authenticated"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"method": "DELETE",
|
|
14
|
+
"path": "/products/:id"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"method": "GET",
|
|
18
|
+
"path": "/products",
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"method": "GET",
|
|
23
|
+
"path": "/admin",
|
|
24
|
+
"access": "admin"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"method": "GET",
|
|
28
|
+
"path": "/admin",
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "permission-gap-detector",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Check declared API route permissions for missing policies, duplicates, and conflicts.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"permission-gap": "bin/cli.js",
|
|
9
|
+
"permission-gap-detector": "bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"bin/",
|
|
14
|
+
"examples/",
|
|
15
|
+
"CHANGELOG.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test",
|
|
19
|
+
"prepublishOnly": "npm test"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"permissions",
|
|
23
|
+
"routes",
|
|
24
|
+
"express",
|
|
25
|
+
"audit",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Aditya030207",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/Aditya030207/npm-package-project.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/Aditya030207/npm-package-project/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/Aditya030207/npm-package-project#readme",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org/"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { validateRoutes, checkPermissions } = require('./checkPermissions');
|
|
2
|
+
const checkDuplicates = require('./checkDuplicates');
|
|
3
|
+
|
|
4
|
+
function auditRoutes(routes) {
|
|
5
|
+
const { validRoutes, invalidRoutes } = validateRoutes(routes);
|
|
6
|
+
const { validPermissions, missingPermissions, invalidPermissions } = checkPermissions(validRoutes);
|
|
7
|
+
const { duplicateRoutes, conflictingPermissions } = checkDuplicates(validRoutes);
|
|
8
|
+
const findings = { missingPermissions, invalidPermissions, invalidRoutes, duplicateRoutes, conflictingPermissions };
|
|
9
|
+
return {
|
|
10
|
+
summary: {
|
|
11
|
+
totalRoutes: routes.length,
|
|
12
|
+
validPermissions,
|
|
13
|
+
missingPermissions: missingPermissions.length,
|
|
14
|
+
invalidPermissions: invalidPermissions.length,
|
|
15
|
+
invalidRoutes: invalidRoutes.length,
|
|
16
|
+
duplicateRoutes: duplicateRoutes.reduce((sum, group) => sum + group.extraDefinitions, 0),
|
|
17
|
+
conflictingRoutes: conflictingPermissions.length,
|
|
18
|
+
coveragePercentage: routes.length === 0 ? 0 : Math.round(validPermissions / routes.length * 1000) / 10
|
|
19
|
+
},
|
|
20
|
+
findings,
|
|
21
|
+
hasIssues: Object.values(findings).some(items => items.length > 0)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = auditRoutes;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { ACCESS_TYPES } = require('./checkPermissions');
|
|
2
|
+
|
|
3
|
+
function checkDuplicates(routes) {
|
|
4
|
+
const groups = new Map();
|
|
5
|
+
for (const route of routes) {
|
|
6
|
+
const key = `${route.method} ${route.path}`;
|
|
7
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
8
|
+
groups.get(key).push(route);
|
|
9
|
+
}
|
|
10
|
+
const duplicateRoutes = [];
|
|
11
|
+
const conflictingPermissions = [];
|
|
12
|
+
for (const group of groups.values()) {
|
|
13
|
+
if (group.length < 2) continue;
|
|
14
|
+
const { method, path } = group[0];
|
|
15
|
+
const indices = group.map(route => route.index);
|
|
16
|
+
duplicateRoutes.push({ method, path, indices, extraDefinitions: group.length - 1 });
|
|
17
|
+
// Only supported policies can conflict; other values have their own findings.
|
|
18
|
+
const permissions = [...new Set(group.map(route => route.access).filter(access => ACCESS_TYPES.includes(access)))];
|
|
19
|
+
if (permissions.length > 1) {
|
|
20
|
+
conflictingPermissions.push({ method, path, indices, permissions });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { duplicateRoutes, conflictingPermissions };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = checkDuplicates;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const ACCESS_TYPES = ['public', 'authenticated', 'admin'];
|
|
2
|
+
const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'];
|
|
3
|
+
|
|
4
|
+
function validateRoutes(routes) {
|
|
5
|
+
if (!Array.isArray(routes)) throw new TypeError('Routes must be an array.');
|
|
6
|
+
const validRoutes = [];
|
|
7
|
+
const invalidRoutes = [];
|
|
8
|
+
for (let index = 0; index < routes.length; index++) {
|
|
9
|
+
const route = routes[index];
|
|
10
|
+
if (!route || typeof route !== 'object' || Array.isArray(route) ||
|
|
11
|
+
typeof route.method !== 'string' || !METHODS.includes(route.method.toUpperCase()) ||
|
|
12
|
+
typeof route.path !== 'string' || !route.path.startsWith('/') || /[\s\x00-\x1f\x7f-\x9f]/.test(route.path)) {
|
|
13
|
+
invalidRoutes.push({ index, message: 'Expected an object with a supported HTTP method and a path starting with / without whitespace or control characters.' });
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
validRoutes.push({ index, method: route.method.toUpperCase(), path: route.path, access: route.access });
|
|
17
|
+
}
|
|
18
|
+
return { validRoutes, invalidRoutes };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkPermissions(routes) {
|
|
22
|
+
const missingPermissions = [];
|
|
23
|
+
const invalidPermissions = [];
|
|
24
|
+
let validPermissions = 0;
|
|
25
|
+
for (const route of routes) {
|
|
26
|
+
const { index, method, path, access } = route;
|
|
27
|
+
if (access === undefined || access === null || (typeof access === 'string' && access.trim() === '')) {
|
|
28
|
+
missingPermissions.push({ index, method, path });
|
|
29
|
+
} else if (!ACCESS_TYPES.includes(access)) {
|
|
30
|
+
invalidPermissions.push({ index, method, path, access });
|
|
31
|
+
} else {
|
|
32
|
+
validPermissions++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { validPermissions, missingPermissions, invalidPermissions };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { validateRoutes, checkPermissions, ACCESS_TYPES };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function generateReport(result) {
|
|
2
|
+
const { summary: s, findings: f } = result;
|
|
3
|
+
const lines = [
|
|
4
|
+
'==================================', ' PERMISSION GAP DETECTOR', '==================================', '',
|
|
5
|
+
`Total Routes Checked: ${s.totalRoutes}`, `Valid Permissions: ${s.validPermissions}`,
|
|
6
|
+
`Missing Permissions: ${s.missingPermissions}`, `Invalid Permissions: ${s.invalidPermissions}`,
|
|
7
|
+
`Invalid Route Definitions: ${s.invalidRoutes}`, `Duplicate Routes (extra definitions): ${s.duplicateRoutes}`,
|
|
8
|
+
`Conflicting Policies (route groups): ${s.conflictingRoutes}`, '',
|
|
9
|
+
`Permission Coverage: ${s.coveragePercentage}%`, '', 'Issues Found:'
|
|
10
|
+
];
|
|
11
|
+
for (const route of f.missingPermissions) lines.push(`[WARNING] ${route.method} ${route.path}: Missing permission declaration (index ${route.index}).`);
|
|
12
|
+
for (const route of f.invalidPermissions) lines.push(`[ERROR] ${route.method} ${route.path}: Invalid permission; use public, authenticated, or admin (index ${route.index}).`);
|
|
13
|
+
for (const route of f.invalidRoutes) lines.push(`[ERROR] Index ${route.index}: ${route.message}`);
|
|
14
|
+
for (const route of f.duplicateRoutes) lines.push(`[WARNING] ${route.method} ${route.path}: Duplicate route definition (indices ${route.indices.join(', ')}).`);
|
|
15
|
+
for (const route of f.conflictingPermissions) lines.push(`[ERROR] ${route.method} ${route.path}: Conflicting permissions: ${route.permissions.join(', ')}.`);
|
|
16
|
+
if (!result.hasIssues) lines.push('No issues found.');
|
|
17
|
+
lines.push('', '==================================', 'Audit Complete', '==================================');
|
|
18
|
+
return lines.join('\n');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = generateReport;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const auditRoutes = require('./auditRoutes');
|
|
2
|
+
|
|
3
|
+
function findMissingPermissions(routes) {
|
|
4
|
+
return auditRoutes(routes).findings.missingPermissions;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function findDuplicateRoutes(routes) {
|
|
8
|
+
return auditRoutes(routes).findings.duplicateRoutes;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function findConflictingPermissions(routes) {
|
|
12
|
+
return auditRoutes(routes).findings.conflictingPermissions;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { auditRoutes, findMissingPermissions, findDuplicateRoutes, findConflictingPermissions };
|