permission-gap-detector 0.0.0-stage → 1.0.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 +9 -0
- package/LICENSE +21 -0
- package/PROJECT_PROGRESS.md +21 -0
- package/README.md +251 -2
- package/VIVA_GUIDE.md +52 -0
- package/bin/cli.js +34 -0
- package/examples/basic.js +3 -0
- package/examples/routes.json +31 -0
- package/package.json +45 -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,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 — prepared 19 October 2026 (npm publication pending)
|
|
4
|
+
|
|
5
|
+
- Add missing and invalid permission checks.
|
|
6
|
+
- Add duplicate and conflicting policy detection.
|
|
7
|
+
- Add summary counts and declaration coverage.
|
|
8
|
+
- Add CommonJS API and CLI with JSON output and exit codes.
|
|
9
|
+
- Add 20 tests, examples, and college evaluation documentation.
|
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.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Project progress
|
|
2
|
+
|
|
3
|
+
## Intended schedule: 4–26 October 2026
|
|
4
|
+
|
|
5
|
+
These are planned milestones, not claims that work occurred on those dates.
|
|
6
|
+
|
|
7
|
+
| Planned dates | Milestones |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| October 4–6: Planning | Choose package idea; define problem statement; plan features. |
|
|
10
|
+
| October 7–10: Basic Setup | Initialize Node.js project; design route format; create package structure. |
|
|
11
|
+
| October 11–14: Core Development | Missing permission detection; permission validation; duplicate detection. |
|
|
12
|
+
| October 15–18: Remaining Features | Permission conflicts; coverage calculation; report output. |
|
|
13
|
+
| October 19–21: Testing | CLI; unit tests; input validation; bug fixes. |
|
|
14
|
+
| October 22–24: Documentation | README; usage examples; installation instructions; final testing. |
|
|
15
|
+
| October 25–26: Publication and Submission | NPM packaging; public GitHub repository; NPM publication; final demonstration. |
|
|
16
|
+
|
|
17
|
+
## Actual work
|
|
18
|
+
|
|
19
|
+
19 October 2026: Implemented the API and CLI, wrote and passed 20 automated tests, added examples, README, MIT license, changelog, and viva guide. Git commits use their real creation timestamps. Implementation was completed earlier than some planned milestones.
|
|
20
|
+
|
|
21
|
+
npm publication remains pending npm authentication. Publication and submission must not be presented as completed until separately verified.
|
package/README.md
CHANGED
|
@@ -1,3 +1,252 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Permission Gap Detector
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small, dependency-free CommonJS Node.js package that checks **declared API route permissions**. Built as an Advanced Software Development college assignment.
|
|
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
|
+
The release is prepared as version 1.0.0. **npm publication is pending account authentication.** Once published:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install permission-gap-detector
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Until then, 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.0.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 |
|
|
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
|
+
After npm publication:
|
|
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` after publication, then run `permission-gap check routes.json`. Both binary names point to the same CLI.
|
|
136
|
+
|
|
137
|
+
From this repository (works before publication):
|
|
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 --version
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Files must contain a JSON array, not an object wrapping the array. Paths are relative to the current working directory; quote paths containing spaces. 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.
|
|
147
|
+
|
|
148
|
+
## Example output
|
|
149
|
+
|
|
150
|
+
Running the included six-definition example produces:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
==================================
|
|
154
|
+
PERMISSION GAP DETECTOR
|
|
155
|
+
==================================
|
|
156
|
+
|
|
157
|
+
Total Routes Checked: 6
|
|
158
|
+
Valid Permissions: 5
|
|
159
|
+
Missing Permissions: 1
|
|
160
|
+
Invalid Permissions: 0
|
|
161
|
+
Invalid Route Definitions: 0
|
|
162
|
+
Duplicate Routes (extra definitions): 2
|
|
163
|
+
Conflicting Policies (route groups): 1
|
|
164
|
+
|
|
165
|
+
Permission Coverage: 83.3%
|
|
166
|
+
|
|
167
|
+
Issues Found:
|
|
168
|
+
[WARNING] DELETE /products/:id: Missing permission declaration (index 2).
|
|
169
|
+
[WARNING] GET /products: Duplicate route definition (indices 0, 3).
|
|
170
|
+
[WARNING] GET /admin: Duplicate route definition (indices 4, 5).
|
|
171
|
+
[ERROR] GET /admin: Conflicting permissions: admin, public.
|
|
172
|
+
|
|
173
|
+
==================================
|
|
174
|
+
Audit Complete
|
|
175
|
+
==================================
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The example intentionally has issues and exits with code 1.
|
|
179
|
+
|
|
180
|
+
## Development and testing
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
git clone https://github.com/Aditya030207/npm-package-project.git
|
|
184
|
+
cd npm-package-project
|
|
185
|
+
npm ci
|
|
186
|
+
npm test
|
|
187
|
+
node examples/basic.js
|
|
188
|
+
npm pack --dry-run
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The 20 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.
|
|
192
|
+
|
|
193
|
+
## Source structure
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
npm-package-project/
|
|
197
|
+
├── src/
|
|
198
|
+
│ ├── index.js # Four public exports
|
|
199
|
+
│ ├── auditRoutes.js # Combine checks and calculate summary
|
|
200
|
+
│ ├── checkPermissions.js # Validate input and access policies
|
|
201
|
+
│ ├── checkDuplicates.js # Group routes and find conflicts
|
|
202
|
+
│ └── generateReport.js # Format readable terminal output
|
|
203
|
+
├── bin/cli.js # Arguments, JSON file reading, exit codes
|
|
204
|
+
├── tests/package.test.js # API and CLI tests
|
|
205
|
+
├── examples/
|
|
206
|
+
│ ├── basic.js
|
|
207
|
+
│ └── routes.json
|
|
208
|
+
├── package.json
|
|
209
|
+
├── package-lock.json
|
|
210
|
+
├── README.md
|
|
211
|
+
├── CHANGELOG.md
|
|
212
|
+
├── PROJECT_PROGRESS.md
|
|
213
|
+
├── VIVA_GUIDE.md
|
|
214
|
+
├── LICENSE
|
|
215
|
+
└── .gitignore
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Publication
|
|
219
|
+
|
|
220
|
+
From the package directory, authenticate interactively and publish:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm login
|
|
224
|
+
npm whoami
|
|
225
|
+
npm test
|
|
226
|
+
npm pack --dry-run
|
|
227
|
+
npm publish --access public
|
|
228
|
+
npm view permission-gap-detector@1.0.0 version
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Complete any npm browser or two-factor authentication prompts. The name was absent from the npm registry on 19 October 2026; availability is not a reservation. If npm rejects the name, select an available name or your own npm scope and update the package metadata and usage instructions. After successful publication, update this README's pending status. Verify installation in a fresh directory with `npm install permission-gap-detector@1.0.0`, then run the basic usage example and CLI.
|
|
232
|
+
|
|
233
|
+
## College demonstration
|
|
234
|
+
|
|
235
|
+
1. Explain the missing-permission problem and the distinction between declarations and enforcement.
|
|
236
|
+
2. Run `npm test` to show the test results.
|
|
237
|
+
3. Run `node bin/cli.js check examples/routes.json` and explain the missing policy, two extra duplicates, one conflicting group, and 83.3% coverage.
|
|
238
|
+
4. In a copy of the JSON, assign `admin` to the DELETE route and remove the repeated GET definitions. Rerun to show no issues and 100% coverage.
|
|
239
|
+
5. Show the exported functions in `src/index.js` and the grouping loop in `src/checkDuplicates.js`.
|
|
240
|
+
6. Show the GitHub repository and install the packed archive into a separate project. Show the npm listing once publication is complete.
|
|
241
|
+
|
|
242
|
+
## Limitations
|
|
243
|
+
|
|
244
|
+
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.
|
|
245
|
+
|
|
246
|
+
## Contributing
|
|
247
|
+
|
|
248
|
+
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.
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
MIT. See [LICENSE](LICENSE).
|
package/VIVA_GUIDE.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Viva guide
|
|
2
|
+
|
|
3
|
+
**What problem does this package solve?**
|
|
4
|
+
It finds missing or inconsistent access declarations in a developer-maintained API route list.
|
|
5
|
+
|
|
6
|
+
**Why did we create it?**
|
|
7
|
+
It is easy to add an endpoint and forget to record who should access it. This reusable check makes omissions visible before review or release.
|
|
8
|
+
|
|
9
|
+
**How does auditRoutes() work?**
|
|
10
|
+
It checks that the input is an array, separates invalid route structures, checks access values, groups matching routes, and returns findings with counts and coverage. It does not change the input.
|
|
11
|
+
|
|
12
|
+
**How are duplicates detected?**
|
|
13
|
+
A Map groups definitions using the uppercase HTTP method plus the exact path. A group with more than one definition is duplicated. A group of three contributes two extra definitions to the duplicate count.
|
|
14
|
+
|
|
15
|
+
**How are permission conflicts detected?**
|
|
16
|
+
Within each duplicate group, a Set collects distinct supported permissions. More than one supported permission means a conflict. Invalid or missing values have separate findings.
|
|
17
|
+
|
|
18
|
+
**How is coverage calculated?**
|
|
19
|
+
Valid permission definitions / total submitted definitions × 100, rounded to one decimal. Empty input has 0%. Duplicate definitions stay in the denominator and may have valid policies. Coverage describes declaration completeness; even 100% can contain conflicts.
|
|
20
|
+
|
|
21
|
+
**Authentication versus authorization?**
|
|
22
|
+
Authentication checks who a person is, such as verifying their login. Authorization checks what that person may do, such as deleting a user. This package labels intended access but implements neither mechanism.
|
|
23
|
+
|
|
24
|
+
**What is package.json?**
|
|
25
|
+
It describes the npm package: name, version, main module, CLI commands, scripts, license, Node requirement, and repository. The files field controls which project files go into the published archive.
|
|
26
|
+
|
|
27
|
+
**What does module.exports do?**
|
|
28
|
+
It makes functions available to other files through CommonJS require(). Our entry point exports four named functions.
|
|
29
|
+
|
|
30
|
+
**How do automated tests work?**
|
|
31
|
+
Node's built-in runner executes test cases. Assertions compare actual behavior with expected results. CLI tests launch a real child process and use temporary files. npm test runs node --test. A failing assertion makes the test command fail.
|
|
32
|
+
|
|
33
|
+
**Why no dependencies?**
|
|
34
|
+
The checks need only loops, arrays, Map, and Set. Node already provides file reading, process arguments, and a test runner.
|
|
35
|
+
|
|
36
|
+
**How is an npm package published?**
|
|
37
|
+
Choose an available name, prepare metadata and files, run tests and npm pack --dry-run, log in with npm login, then run npm publish --access public. Complete required authentication prompts and verify the registry version and a fresh install. This project's publication is pending login; do not claim it is already published.
|
|
38
|
+
|
|
39
|
+
**What is version 1.0.0?**
|
|
40
|
+
It is the first working release. Semantic versioning uses major.minor.patch: breaking API changes increase major, compatible features increase minor, and compatible fixes increase patch.
|
|
41
|
+
|
|
42
|
+
**What do CLI exit codes mean?**
|
|
43
|
+
Zero means the audit found no issues; one means findings were detected; two means an input or command error. Other scripts can use these statuses.
|
|
44
|
+
|
|
45
|
+
**What are the limitations?**
|
|
46
|
+
The package sees only supplied declarations. It does not discover routes or verify middleware, authentication, authorization, or overall security. Paths are compared exactly; it does not understand Express routing semantics.
|
|
47
|
+
|
|
48
|
+
**How would you demonstrate reuse?**
|
|
49
|
+
Install the archive in an empty project, require permission-gap-detector, call auditRoutes with a route array, and show the summary. Then run the installed CLI on the same route data.
|
|
50
|
+
|
|
51
|
+
**What is the approximate algorithmic cost?**
|
|
52
|
+
Validation and permission checks visit each entry once. Grouping uses a Map; checking policies visits each group entry once. Typical time and memory are linear in the number of definitions, with string handling also depending on path length.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
const json = args.includes('--json');
|
|
8
|
+
const usage = 'Usage: permission-gap check <routes.json> [--json]\nOptions: --help, --version, --json';
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
if (args.length === 1 && args[0] === '--help') {
|
|
12
|
+
console.log(usage);
|
|
13
|
+
} else if (args.length === 1 && args[0] === '--version') {
|
|
14
|
+
console.log(version);
|
|
15
|
+
} else {
|
|
16
|
+
const positional = args.filter(arg => arg !== '--json');
|
|
17
|
+
if (positional.length !== 2 || positional[0] !== 'check' || positional[1].startsWith('--') || args.filter(arg => arg === '--json').length > 1) {
|
|
18
|
+
throw new Error(usage);
|
|
19
|
+
}
|
|
20
|
+
let content;
|
|
21
|
+
try { content = fs.readFileSync(positional[1], 'utf8'); }
|
|
22
|
+
catch { throw new Error(`Cannot read file: ${positional[1]}`); }
|
|
23
|
+
let routes;
|
|
24
|
+
try { routes = JSON.parse(content); }
|
|
25
|
+
catch { throw new Error(`Invalid JSON in file: ${positional[1]}`); }
|
|
26
|
+
const result = auditRoutes(routes);
|
|
27
|
+
console.log(json ? JSON.stringify(result, null, 2) : generateReport(result));
|
|
28
|
+
process.exitCode = result.hasIssues ? 1 : 0;
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (json) console.log(JSON.stringify({ error: error.message }));
|
|
32
|
+
else console.error(`Error: ${error.message}`);
|
|
33
|
+
process.exitCode = 2;
|
|
34
|
+
}
|
|
@@ -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,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "permission-gap-detector",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"VIVA_GUIDE.md",
|
|
17
|
+
"PROJECT_PROGRESS.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test",
|
|
21
|
+
"prepublishOnly": "npm test"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"permissions",
|
|
25
|
+
"routes",
|
|
26
|
+
"express",
|
|
27
|
+
"audit",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"author": "Aditya030207",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/Aditya030207/npm-package-project.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/Aditya030207/npm-package-project/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/Aditya030207/npm-package-project#readme",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"registry": "https://registry.npmjs.org/"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -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/.test(route.path)) {
|
|
13
|
+
invalidRoutes.push({ index, message: 'Expected an object with a supported HTTP method and a path starting with / without whitespace.' });
|
|
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 };
|