server-action-audit 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.fr.md +55 -0
- package/README.md +182 -2
- package/dist/chunk-RCEEE6TI.js +677 -0
- package/dist/chunk-RCEEE6TI.js.map +1 -0
- package/dist/cli.js +1106 -0
- package/dist/cli.js.map +1 -0
- package/dist/eslint.cjs +796 -0
- package/dist/eslint.cjs.map +1 -0
- package/dist/eslint.d.cts +15 -0
- package/dist/eslint.d.ts +15 -0
- package/dist/eslint.js +112 -0
- package/dist/eslint.js.map +1 -0
- package/dist/index.cjs +1045 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +153 -0
- package/dist/index.d.ts +153 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Del Prudence
|
|
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.fr.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# server-action-audit
|
|
2
|
+
|
|
3
|
+
**Trouvez les Server Actions Next.js que n'importe qui peut appeler.**
|
|
4
|
+
|
|
5
|
+
Chaque fonction `"use server"` est un point d'accès HTTP public. N'importe qui peut l'appeler avec une requête POST, hors de votre interface, avec les arguments de son choix. La [documentation Next.js](https://nextjs.org/docs/app/guides/data-security) est claire : vérifier la connexion au niveau de la page ne protège pas les Server Actions qu'elle contient. Chaque action doit vérifier la session elle-même.
|
|
6
|
+
|
|
7
|
+
Cette vérification s'oublie facilement, et rien ne vous le rappelle. `server-action-audit` liste toutes les Server Actions de votre application et signale celles qui ne vérifient pas qui les appelle : dans le terminal, en CI, et dans votre éditeur grâce à ESLint.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx server-action-audit
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
Aucune configuration nécessaire. L'outil lit votre code, il ne l'exécute jamais. Testé sur cinq grosses applications Next.js open source : 456 Server Actions, environ une seconde par application.
|
|
16
|
+
|
|
17
|
+
## Ce qu'il reconnaît
|
|
18
|
+
|
|
19
|
+
- **Les actions** : les exports des fichiers `"use server"`, et les actions déclarées dans un composant (`<form action={async () => { "use server" … }}>`).
|
|
20
|
+
- **Les bibliothèques d'authentification** : Auth.js / NextAuth, Clerk, Supabase, Better Auth, Lucia, Kinde, iron-session, Firebase Admin, WorkOS AuthKit.
|
|
21
|
+
- **Vos propres fonctions** (`getCurrentUser()`, `requireUser()`…) : il les suit d'un fichier à l'autre, à travers les alias `@/` du tsconfig, les ré-exports et `cache()` de React.
|
|
22
|
+
- **Les clients d'actions** : next-safe-action et zsa. L'outil lit leur middleware, y compris celui d'un client dérivé d'un autre client.
|
|
23
|
+
- **Seuls les contrôles du chemin principal comptent** : une vérification placée dans une branche `if` ou dans une fonction de rappel ne protège pas l'action.
|
|
24
|
+
- **La validation des entrées** : il prévient quand une action utilise ses arguments sans les valider (Zod, Valibot, Yup, ou un schéma next-safe-action / zsa).
|
|
25
|
+
|
|
26
|
+
## Actions publiques
|
|
27
|
+
|
|
28
|
+
Login, inscription, newsletter : certaines actions sont publiques exprès. Indiquez-le avec un commentaire juste avant l'action, ou en haut de son corps :
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Tout le monde peut s'abonner. @public-action
|
|
32
|
+
export async function subscribe(email: string) {}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration, ESLint et CI
|
|
36
|
+
|
|
37
|
+
Tout est décrit dans le [README en anglais](README.md#configuration) :
|
|
38
|
+
- le fichier `server-action-audit.config.json` ;
|
|
39
|
+
- le plugin ESLint (`server-action-audit/eslint`) ;
|
|
40
|
+
- les options `--fail-on`, `--json` et `--quiet` ;
|
|
41
|
+
- l'Action GitHub, qui annote les pull requests et peut aussi y poster le rapport en commentaire (`comment: true`).
|
|
42
|
+
|
|
43
|
+
## Limites
|
|
44
|
+
|
|
45
|
+
C'est une analyse statique, fondée sur les noms. Un rapport sans erreur veut dire « rien d'évident ne manque », pas « l'application est sûre ».
|
|
46
|
+
|
|
47
|
+
- **Elle ne vérifie pas ce que vous faites de la session.**
|
|
48
|
+
- **Elle ne vérifie pas l'appartenance** : être connecté ne veut pas dire avoir le droit de supprimer *ce* contenu.
|
|
49
|
+
- **Elle ne compte pas le middleware**, car la documentation de Next.js recommande de vérifier dans chaque action.
|
|
50
|
+
|
|
51
|
+
Un faux positif, ou une action protégée qui n'est pas reconnue ? [Ouvrez une issue](https://github.com/i6moons/server-action-audit/issues).
|
|
52
|
+
|
|
53
|
+
## Licence
|
|
54
|
+
|
|
55
|
+
[MIT](LICENSE) © Del Prudence ([@i6moons](https://github.com/i6moons))
|
package/README.md
CHANGED
|
@@ -1,3 +1,183 @@
|
|
|
1
|
-
#
|
|
1
|
+
# server-action-audit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Find the Next.js Server Actions that anyone can call.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/i6moons/server-action-audit/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/server-action-audit)
|
|
7
|
+
[](https://github.com/marketplace/actions/server-action-audit)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
Every `"use server"` function is a public HTTP endpoint. Anyone can call it with a POST request, from outside your UI, with any arguments. The [Next.js docs](https://nextjs.org/docs/app/guides/data-security) put it plainly: an auth check on the page does not protect the Server Actions defined in it, so each action has to check the session itself.
|
|
11
|
+
|
|
12
|
+
That check is easy to forget, and nothing reminds you. `server-action-audit` lists every Server Action in your app and flags the ones without an authorization check. It runs in your terminal, in CI, and in your editor through ESLint.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx server-action-audit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
No config needed. It reads your code; it never runs it. Tested on five large open-source Next.js apps: 456 Server Actions, about a second per app.
|
|
21
|
+
|
|
22
|
+
## What it understands
|
|
23
|
+
|
|
24
|
+
| | |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| **Actions** | Exports of `"use server"` files (functions, arrow functions, `export { a as b }`, default exports) and inline actions (`<form action={async () => { "use server" … }}>`) |
|
|
27
|
+
| **Auth libraries** | Auth.js / NextAuth v5 (`auth()`), NextAuth v4 (`getServerSession`), Clerk (`auth()`, `currentUser()`, `auth.protect()`), Supabase (`supabase.auth.getUser()`, `getClaims()`), Better Auth (`auth.api.getSession()`), Lucia (`validateRequest()`), Kinde, iron-session, Firebase Admin (`verifyIdToken`, `verifySessionCookie`), WorkOS AuthKit (`withAuth()`) |
|
|
28
|
+
| **Your own helpers** | `getCurrentUser()`, `requireUser()`, `verifySession()`… and any helper that calls one of the above. It follows them across files (relative imports and tsconfig `paths` like `@/lib/dal`), through re-exports and React `cache()`, up to 4 levels deep. Only checks on the main path count: a check inside an `if` branch or a callback does not |
|
|
29
|
+
| **Inline actions** | An action declared in a component that uses the component's session (`const session = await auth()` in the page, `if (!session) return` in the action) |
|
|
30
|
+
| **Action clients** | [next-safe-action](https://next-safe-action.dev) and [zsa](https://github.com/IdoPesok/zsa). It reads the client's middleware, so `actionClient.action(…)` is protected when `actionClient` checks the session, and so is a client derived from it (`actionClient.use(…)`). Guards that keep logged-in users out (`throwIfAuthenticated`) do not count |
|
|
31
|
+
| **Wrappers** | `withAuth(async () => …)`, `validatedActionWithUser(schema, …)`: wrappers whose name says auth, or that you list in the config |
|
|
32
|
+
| **Input validation** | Warns when an action takes arguments and nothing parses them (Zod, Valibot, Yup, or a next-safe-action / zsa schema) |
|
|
33
|
+
|
|
34
|
+
## Public actions
|
|
35
|
+
|
|
36
|
+
Some actions are public on purpose: login, sign-up, newsletter. Say so with a comment, right before the action or at the top of its body:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// Anyone can subscribe. @public-action
|
|
40
|
+
export async function subscribe(email: string) {
|
|
41
|
+
const { email: valid } = SubscribeSchema.parse({ email })
|
|
42
|
+
// …
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
They show up as `○ public` and never fail the audit. A real auth check always wins over the marker.
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
Teach it your own names in `server-action-audit.config.json`, or under `"serverActionAudit"` in package.json:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"authFunctions": ["getMe", "session.require"],
|
|
55
|
+
"authWrappers": ["withTeam"],
|
|
56
|
+
"authClients": ["teamActionClient"],
|
|
57
|
+
"publicActions": ["subscribe"],
|
|
58
|
+
"ignore": ["app/legacy/**"]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **`authFunctions`**: a plain name matches the end of the call (`getMe` matches `getMe()` and `lib.getMe()`). A dotted name matches the end of the path (`session.require` matches `ctx.session.require()`).
|
|
63
|
+
- **`authWrappers`**: functions that wrap an action with an auth check.
|
|
64
|
+
- **`authClients`**: next-safe-action or zsa clients with auth middleware.
|
|
65
|
+
- **`publicActions`**: action names to treat as public.
|
|
66
|
+
- **`ignore`**: globs, relative to the project root.
|
|
67
|
+
|
|
68
|
+
When an action isn't recognized, the report tells you which name to add.
|
|
69
|
+
|
|
70
|
+
## ESLint
|
|
71
|
+
|
|
72
|
+
The same analysis, in your editor. ESLint 9 or 10, flat config:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// eslint.config.mjs
|
|
76
|
+
import serverActionAudit from 'server-action-audit/eslint'
|
|
77
|
+
|
|
78
|
+
export default [
|
|
79
|
+
// …your config
|
|
80
|
+
serverActionAudit.configs.recommended,
|
|
81
|
+
]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Rule | Default | |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `server-action-audit/require-auth` | error | Server Action without an authorization check |
|
|
87
|
+
| `server-action-audit/require-input-validation` | warn | Server Action that uses its arguments without validating them |
|
|
88
|
+
|
|
89
|
+
Options go in `settings.serverActionAudit` (shared by both rules) or on each rule. They use the same format as the config file, without `ignore`.
|
|
90
|
+
|
|
91
|
+
## CI
|
|
92
|
+
|
|
93
|
+
The CLI exits with code 1 when it finds an unprotected action, so any CI can run it:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
npx server-action-audit
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### GitHub Actions
|
|
100
|
+
|
|
101
|
+
Problems show up as annotations on the pull request, right on the line of the action:
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
# .github/workflows/security.yml
|
|
105
|
+
name: Security
|
|
106
|
+
on: [pull_request]
|
|
107
|
+
jobs:
|
|
108
|
+
server-actions:
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/checkout@v7
|
|
112
|
+
- uses: i6moons/server-action-audit@v0
|
|
113
|
+
with:
|
|
114
|
+
path: . # where your Next.js app is
|
|
115
|
+
fail-on: unprotected
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
To also get the report as a comment on the pull request, set `comment: true` and let the job write to pull requests. The action keeps a single comment and updates it on every push. It only comments once there is something to fix. If the job can't comment (pull requests from forks only get a read-only token), it shows a warning and the audit result stays the same.
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
jobs:
|
|
122
|
+
server-actions:
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
permissions:
|
|
125
|
+
contents: read
|
|
126
|
+
pull-requests: write
|
|
127
|
+
steps:
|
|
128
|
+
- uses: actions/checkout@v7
|
|
129
|
+
- uses: i6moons/server-action-audit@v0
|
|
130
|
+
with:
|
|
131
|
+
comment: true
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For GitHub code scanning (the Security tab), upload the SARIF output:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
- run: npx server-action-audit --format sarif > server-actions.sarif || true
|
|
138
|
+
- uses: github/codeql-action/upload-sarif@v4
|
|
139
|
+
with:
|
|
140
|
+
sarif_file: server-actions.sarif
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Options
|
|
144
|
+
|
|
145
|
+
| Option | |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `--fail-on unprotected` | Default. Fail on actions without an auth check |
|
|
148
|
+
| `--fail-on unvalidated` | Also fail on unvalidated input |
|
|
149
|
+
| `--fail-on never` | Report only |
|
|
150
|
+
| `--format pretty` | Default. Human-readable report |
|
|
151
|
+
| `--format json` | Machine-readable output (`--json` works too) |
|
|
152
|
+
| `--format github` | GitHub Actions annotations |
|
|
153
|
+
| `--format sarif` | SARIF 2.1.0, for code scanning |
|
|
154
|
+
| `--format markdown` | Markdown, e.g. `>> "$GITHUB_STEP_SUMMARY"` |
|
|
155
|
+
| `--pr-comment` | In GitHub Actions, also post the report on the pull request (needs `GITHUB_TOKEN` with `pull-requests: write`) |
|
|
156
|
+
| `--quiet` | Show problems only |
|
|
157
|
+
| `--config <file>` | Another config file |
|
|
158
|
+
|
|
159
|
+
## What it does not check
|
|
160
|
+
|
|
161
|
+
This is a static check that works on names. Treat a green report as "nothing obviously missing", not as proof that your app is secure.
|
|
162
|
+
|
|
163
|
+
- **It does not check what you do with the session.** `await auth()` counts as a check even if you then ignore the result. Make sure you stop when there is no user.
|
|
164
|
+
- **It does not check ownership.** Being logged in is not the same as being allowed to delete *this* post. Check that the resource belongs to the user.
|
|
165
|
+
- **Middleware is not counted.** The Next.js docs recommend checking inside each action, and middleware-only protection has been bypassed before ([CVE-2025-29927](https://nextjs.org/blog/cve-2025-29927)). If you rely on it anyway, mark those actions with `@public-action` or list your middleware helper in `authFunctions`.
|
|
166
|
+
- **Unusual patterns can slip through.** Dynamic imports, auth checks hidden in packages from `node_modules`, or a helper whose name contains `auth` without doing any check.
|
|
167
|
+
|
|
168
|
+
Found a false positive or a false negative? [Open an issue](https://github.com/i6moons/server-action-audit/issues) with the smallest snippet that reproduces it.
|
|
169
|
+
|
|
170
|
+
## API
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { scanProject, formatPretty } from 'server-action-audit'
|
|
174
|
+
|
|
175
|
+
const report = scanProject(process.cwd(), { authFunctions: ['getMe'] })
|
|
176
|
+
console.log(report.summary) // { actions, protected, unprotected, public, unvalidated }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
[MIT](LICENSE) © Del Prudence ([@i6moons](https://github.com/i6moons))
|
|
182
|
+
|
|
183
|
+
[Lire en français](README.fr.md)
|