@ihelpee/prumo-plugin-node-next 0.8.3 → 0.9.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/README.md +33 -28
- package/dist/ast.d.ts +1 -1
- package/dist/ast.d.ts.map +1 -1
- package/dist/rules.d.ts +1 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,31 +9,31 @@
|
|
|
9
9
|
|
|
10
10
|
> Official Next.js **App Router** architectural invariant plugin for [Prumo](https://www.npmjs.com/package/@ihelpee/prumo) — the deterministic gatekeeper and business guardrails for AI-assisted engineering.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
In the App Router, a **Server Action is a public HTTP endpoint disguised as an internal function**, and `"use client"` marks a **bundle entry point**, not a file. An AI code-generation agent writes the action as if it were a trusted internal call; the compiler turns every `"use server"` `export` into a `POST` reachable by anyone. This plugin polices that boundary with pure static AST — **no Next boot, no `react`, no `next build`**.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Invariants
|
|
15
15
|
|
|
16
|
-
| id | sev. |
|
|
16
|
+
| id | sev. | the hidden constructive anti-pattern | the honest boundary (what it does NOT catch) |
|
|
17
17
|
|---|---|---|---|
|
|
18
|
-
| `server-action-mutation-guarded` | error | Server Action / Route Handler
|
|
19
|
-
| `client-boundary-leak-prevented` | error |
|
|
20
|
-
| `server-action-input-validated` | error | Server Action
|
|
21
|
-
| `middleware-not-sole-auth` | error |
|
|
22
|
-
| `next-public-env-not-secret` | error | `NEXT_PUBLIC_*`
|
|
18
|
+
| `server-action-mutation-guarded` | error | A mutating Server Action / Route Handler writes to the database without re-verifying auth **in its own body, before the write** | pure read (`GET`); guard delegated to a `server-only` DAL that does the auth internally |
|
|
19
|
+
| `client-boundary-leak-prevented` | error | the client graph (reachable from a `"use client"`) imports an ORM/database client, a secret module, or reads non-`NEXT_PUBLIC_` `process.env` | shared module without ORM/secret; `await import(variableName)` (declared limitation) |
|
|
20
|
+
| `server-action-input-validated` | error | A Server Action mutates state without validating input via a schema (Zod/Valibot) **before** the database | a schema validates **shape, not ownership** — IDOR is a separate check, never reported as "safe" |
|
|
21
|
+
| `middleware-not-sole-auth` | error | authorization sustained **only** by `middleware.ts`/`proxy.ts`, with no re-verification in the actions/handlers | endpoints that re-verify auth within their own body |
|
|
22
|
+
| `next-public-env-not-secret` | error | a `NEXT_PUBLIC_*` with a secret name (`KEY`/`SECRET`/`TOKEN`/`DATABASE_URL`/...) is inlined into the bundle at build time | a genuinely public `NEXT_PUBLIC_*` (e.g.: `NEXT_PUBLIC_SITE_URL`) |
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Compatible with **Next 14 / 15 / 16**: the extractor recognizes `middleware.ts` **and** `proxy.ts` (Next 16 renamed middleware→proxy) as the same class, and accepts both `cookies()` and `await cookies()` (Next 15 async).
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## Installation
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
pnpm add -D @ihelpee/prumo-plugin-node-next @ihelpee/prumo
|
|
30
|
-
#
|
|
30
|
+
# or
|
|
31
31
|
npm install --save-dev @ihelpee/prumo-plugin-node-next @ihelpee/prumo
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Usage
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
In your Next app's `prumo.config.ts`:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
39
|
import { defineConfig } from '@ihelpee/prumo';
|
|
@@ -49,66 +49,71 @@ export default defineConfig({
|
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
In `.prumo/invariants/rules.ts`, apply the preset:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
55
|
import { withWaivers } from '@ihelpee/prumo';
|
|
56
56
|
import { nextPreset } from '@ihelpee/prumo-plugin-node-next';
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// A narrow waiver is visible debt; a broad one is the gate switched off with a pretty name.
|
|
59
59
|
export default withWaivers(nextPreset, '.prumo/waivers/legacy.json', [
|
|
60
60
|
'server-action-mutation-guarded',
|
|
61
61
|
]);
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Or register the whole plugin (extractor + rules) via the contract:
|
|
65
65
|
|
|
66
66
|
```ts
|
|
67
67
|
import next from '@ihelpee/prumo-plugin-node-next';
|
|
68
|
-
// prumo plugin node-next →
|
|
68
|
+
// prumo plugin node-next → shows the catalog and the config snippet
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Run the gate:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
npx prumo gate
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
##
|
|
77
|
+
## Example — the finding a mocked unit test never sees
|
|
78
78
|
|
|
79
79
|
```ts
|
|
80
80
|
// app/posts/[id]/page.tsx (Server Component)
|
|
81
81
|
export default async function PostPage({ params }) {
|
|
82
82
|
const { id } = await params;
|
|
83
83
|
async function deletePost(formData: FormData) {
|
|
84
|
-
'use server'; // ←
|
|
84
|
+
'use server'; // ← becomes a public POST
|
|
85
85
|
const postId = String(formData.get('id'));
|
|
86
|
-
await db.post.delete({ where: { id: postId } }); // ←
|
|
86
|
+
await db.post.delete({ where: { id: postId } }); // ← no auth(), no schema
|
|
87
87
|
}
|
|
88
88
|
return <form action={deletePost}>…</form>;
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
The happy-path test ("logged in → clicks → 200") **confirms the agent's belief**. The gate denies it:
|
|
93
93
|
|
|
94
94
|
```
|
|
95
95
|
server-action-mutation-guarded app/posts/[id]/page.tsx:6 deletePost
|
|
96
96
|
server-action-input-validated app/posts/[id]/page.tsx:6 deletePost
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
##
|
|
99
|
+
## Oracle: the action is an endpoint, not a function
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
A test that does `import { deletePost } from '@/app/actions'` and calls it as JS **bypasses the public POST** — it is a mirror, not an oracle, and the core's `oracle-isolation` flags it as such (it does not count toward the OII). The canonical oracle is the **negative test**: a `POST` to the action **without a session cookie** must fail. Forged cookies do not count as evidence.
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Author & Maintainer
|
|
104
104
|
|
|
105
105
|
**Alex Gotardi**
|
|
106
106
|
- 📧 Email: [alexgotardi@gmail.com](mailto:alexgotardi@gmail.com)
|
|
107
107
|
- 🐙 GitHub: [@alexgotardi](https://github.com/alexgotardi)
|
|
108
108
|
- 💼 LinkedIn: [linkedin.com/in/alexgotardi](https://www.linkedin.com/in/alexgotardi/)
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
---
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
## License & Brand
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
Copyright © 2026 **IHELPEE TECNOLOGIA LTDA** (<https://ihelpee.com>). All rights reserved.
|
|
115
|
+
Created and authored by [Alex Gotardi](https://github.com/alexgotardi).
|
|
116
|
+
|
|
117
|
+
Licensed under the [Apache License, Version 2.0](https://github.com/ihelpee/prumo/blob/main/LICENSE) (the "License"); you may not use this file except in compliance with the License. A copy is available at <http://www.apache.org/licenses/LICENSE-2.0>.
|
|
118
|
+
|
|
119
|
+
The **"Prumo"** name and logo are trademarks of IHELPEE TECNOLOGIA LTDA and are **not** licensed under the Apache 2.0 license.
|
package/dist/ast.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface NextFile {
|
|
|
26
26
|
serverComponents: string[];
|
|
27
27
|
envReads: EnvRead[];
|
|
28
28
|
importsServerOnly: boolean;
|
|
29
|
-
/** Import dinâmico/require de string literal que casa ORM_PACKAGES (
|
|
29
|
+
/** Import dinâmico/require de string literal que casa ORM_PACKAGES (linha de marcação §7). */
|
|
30
30
|
dynamicOrmImports: string[];
|
|
31
31
|
}
|
|
32
32
|
export interface RouteHandlerInfo {
|
package/dist/ast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmC,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAElF,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5E;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,oEAAoE;IACpE,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,8DAA8D;IAC9D,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmC,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAElF,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5E;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,oEAAoE;IACpE,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,8DAA8D;IAC9D,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,8FAA8F;IAC9F,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,mFAAmF;IACnF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,eAAO,MAAM,UAAU,aAQrB,CAAC;AAmBH,eAAO,MAAM,YAAY,UAexB,CAAC;AAkDF,wBAAgB,aAAa,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CASlD;AA0HD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,QAAQ,CA0H7J;AAMD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,SAAS,CAsDnF;AAwBD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,CAI1D"}
|
package/dist/rules.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare const serverActionMutationGuarded: RuleDef;
|
|
|
12
12
|
* `process.env` não-`NEXT_PUBLIC_`. Reporta a TRILHA para ser acionável.
|
|
13
13
|
*
|
|
14
14
|
* Não pega: módulo compartilhado sem ORM/segredo importado por client.
|
|
15
|
-
* Limitação declarada: `await import(nomeVariavel)` não resolve (
|
|
15
|
+
* Limitação declarada: `await import(nomeVariavel)` não resolve (linha de marcação só
|
|
16
16
|
* para string literal de ORM).
|
|
17
17
|
*/
|
|
18
18
|
export declare const clientBoundaryLeakPrevented: RuleDef;
|
package/dist/rules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EAGb,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EAGb,MAAM,2BAA2B,CAAC;AAuCnC;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,SAuB9B,CAAC;AAEX;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,SA6D9B,CAAC;AAKX;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,SAmB7B,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,SAsBxB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,SAoBzB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,UAAU,iDAerB,CAAC;AAEH,eAAO,MAAM,UAAU,EAAE,OAAO,EAAqB,CAAC"}
|
package/dist/rules.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rule, definePlugin, } from '@ihelpee/prumo/invariants';
|
|
2
2
|
import { extractNextModel, isOrmSpecifier, } from './ast.js';
|
|
3
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
3
4
|
/**
|
|
4
5
|
* Invariantes do App Router (spec §2). Os cinco ids são canônicos e
|
|
5
6
|
* congelados: nenhum card filho pode renomeá-los sem voltar à especificação.
|
|
@@ -59,7 +60,7 @@ export const serverActionMutationGuarded = rule('server-action-mutation-guarded'
|
|
|
59
60
|
* `process.env` não-`NEXT_PUBLIC_`. Reporta a TRILHA para ser acionável.
|
|
60
61
|
*
|
|
61
62
|
* Não pega: módulo compartilhado sem ORM/segredo importado por client.
|
|
62
|
-
* Limitação declarada: `await import(nomeVariavel)` não resolve (
|
|
63
|
+
* Limitação declarada: `await import(nomeVariavel)` não resolve (linha de marcação só
|
|
63
64
|
* para string literal de ORM).
|
|
64
65
|
*/
|
|
65
66
|
export const clientBoundaryLeakPrevented = rule('client-boundary-leak-prevented')
|
|
@@ -222,7 +223,7 @@ export const nextPublicEnvNotSecret = rule('next-public-env-not-secret')
|
|
|
222
223
|
*/
|
|
223
224
|
export const nextPlugin = definePlugin({
|
|
224
225
|
name: 'node-next',
|
|
225
|
-
version:
|
|
226
|
+
version: pkg.version,
|
|
226
227
|
description: 'Next.js App Router invariants: Server Action auth guards, client boundary leaks, input validation, proxy-not-sole-auth, NEXT_PUBLIC secret hygiene.',
|
|
227
228
|
extractors: {
|
|
228
229
|
next: (ctx) => extractNextModel(ctx.root, (ctx.sourceFiles ?? [])),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ihelpee/prumo-plugin-node-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Next.js App Router plugin for Prumo: Server Action guards, client boundary leaks, and input validation invariants.",
|
|
6
6
|
"author": "Alex Gotardi <alexgotardi@gmail.com> (https://github.com/alexgotardi)",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ihelpee/prumo": "0.
|
|
34
|
+
"@ihelpee/prumo": "0.9.0",
|
|
35
35
|
"ts-morph": "^24.0.0"
|
|
36
36
|
}
|
|
37
37
|
}
|