@multiplier-labs/stepflow 0.3.4 → 0.3.8
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 +9 -0
- package/dist/{chunk-5XJBAPXF.js → chunk-4IDPWWGS.js} +15 -4
- package/dist/chunk-4IDPWWGS.js.map +1 -0
- package/dist/{chunk-U3PCVK6O.js → chunk-T6GAZBTQ.js} +70 -38
- package/dist/chunk-T6GAZBTQ.js.map +1 -0
- package/dist/{chunk-FMPJBK2N.js → chunk-TVSADUYU.js} +3 -5
- package/dist/{chunk-FMPJBK2N.js.map → chunk-TVSADUYU.js.map} +1 -1
- package/dist/events/index.d.ts +43 -3
- package/dist/events/index.js +7 -3
- package/dist/{index-Cd0IZ40L.d.ts → index-CdXDv30T.d.ts} +10 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +159 -46
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/storage/index.d.ts +6 -4
- package/dist/storage/index.js +1 -1
- package/dist/{types-DSEjiLMj.d.ts → types-BfWe5yP6.d.ts} +2 -0
- package/package.json +26 -14
- package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
- package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
- package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
- package/.github/CODEOWNERS +0 -15
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/publish.yml +0 -42
- package/.github/workflows/security.yml +0 -29
- package/CHANGELOG.md +0 -34
- package/SECURITY.md +0 -40
- package/dist/chunk-5XJBAPXF.js.map +0 -1
- package/dist/chunk-U3PCVK6O.js.map +0 -1
- package/docs/github-packages-setup.md +0 -97
- package/docs/stepflow-api-reference.md +0 -1663
- package/src/core/engine/concurrency.ts +0 -229
- package/src/core/engine/lifecycle.ts +0 -176
- package/src/core/engine.test.ts +0 -1435
- package/src/core/engine.ts +0 -598
- package/src/core/index.ts +0 -7
- package/src/core/orchestrator/persistence.ts +0 -165
- package/src/core/orchestrator/plan-resolution.ts +0 -140
- package/src/core/orchestrator/state-transitions.ts +0 -109
- package/src/core/orchestrator/step-runner.ts +0 -427
- package/src/core/orchestrator.ts +0 -252
- package/src/core/types.ts +0 -220
- package/src/events/index.ts +0 -8
- package/src/events/memory.test.ts +0 -107
- package/src/events/memory.ts +0 -92
- package/src/events/socketio.test.ts +0 -378
- package/src/events/socketio.ts +0 -262
- package/src/events/types.ts +0 -118
- package/src/events/webhook.test.ts +0 -949
- package/src/events/webhook.ts +0 -807
- package/src/index.ts +0 -270
- package/src/planning/evaluate-condition.test.ts +0 -153
- package/src/planning/evaluate-condition.ts +0 -105
- package/src/planning/index.ts +0 -55
- package/src/planning/planner.ts +0 -494
- package/src/planning/planning.test.ts +0 -727
- package/src/planning/registry.test.ts +0 -398
- package/src/planning/registry.ts +0 -283
- package/src/planning/types.ts +0 -566
- package/src/scheduler/cron.test.ts +0 -479
- package/src/scheduler/cron.ts +0 -438
- package/src/scheduler/index.ts +0 -8
- package/src/scheduler/postgres-persistence.ts +0 -530
- package/src/scheduler/sqlite-persistence.test.ts +0 -235
- package/src/scheduler/sqlite-persistence.ts +0 -286
- package/src/scheduler/types.ts +0 -73
- package/src/storage/index.ts +0 -27
- package/src/storage/memory.test.ts +0 -349
- package/src/storage/memory.ts +0 -227
- package/src/storage/postgres-core.ts +0 -550
- package/src/storage/postgres-migration.test.ts +0 -69
- package/src/storage/postgres-transaction-pagination.test.ts +0 -107
- package/src/storage/postgres.ts +0 -835
- package/src/storage/sqlite-core.ts +0 -151
- package/src/storage/sqlite.test.ts +0 -642
- package/src/storage/sqlite.ts +0 -717
- package/src/storage/types.ts +0 -343
- package/src/utils/errors.test.ts +0 -150
- package/src/utils/errors.ts +0 -161
- package/src/utils/id.ts +0 -21
- package/src/utils/index.ts +0 -10
- package/src/utils/logger.test.ts +0 -121
- package/src/utils/logger.ts +0 -96
- package/src/utils/pinned-fetch.test.ts +0 -84
- package/src/utils/pinned-fetch.ts +0 -118
- package/src/utils/postgres-deps.ts +0 -79
- package/src/utils/retry.test.ts +0 -177
- package/src/utils/retry.ts +0 -121
- package/src/utils/safe-json.test.ts +0 -166
- package/src/utils/safe-json.ts +0 -107
- package/src/utils/webhook-signature.test.ts +0 -111
- package/src/utils/webhook-signature.ts +0 -74
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -14
- package/vitest.config.ts +0 -9
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# Security Audit: stepflow
|
|
2
|
-
|
|
3
|
-
**Date**: 2026-04-03T20:03:06.542Z
|
|
4
|
-
**Repository**: /srv/repos/stepflow
|
|
5
|
-
**Branch**: main
|
|
6
|
-
**Workflow Run**: 54523493-771d-44b8-b39f-03f2d6caa49e
|
|
7
|
-
**Session**: 53e402d9-8d47-49b4-9a27-03f2ae5ec912
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
I now have all the information needed to produce the report. Here is the security audit:
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Summary
|
|
16
|
-
|
|
17
|
-
**Overall Risk Rating: Low**
|
|
18
|
-
|
|
19
|
-
The Stepflow project is a TypeScript workflow orchestration library with a mature security posture. No critical or high vulnerabilities were identified. The codebase demonstrates intentional, defence-in-depth practices: parameterized queries, SSRF protection with DNS-rebinding prevention, HMAC-SHA256 webhook signing, and delegated authorization. Recent commits (`48e5f0a`, `d61858c`) show that prior SSRF and SQL-injection issues were already discovered and remediated.
|
|
20
|
-
|
|
21
|
-
| Severity | Count |
|
|
22
|
-
|----------|-------|
|
|
23
|
-
| Critical | 0 |
|
|
24
|
-
| High | 0 |
|
|
25
|
-
| Medium | 2 |
|
|
26
|
-
| Low | 3 |
|
|
27
|
-
| Informational | 4 |
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Critical Findings
|
|
32
|
-
|
|
33
|
-
None.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## High Findings
|
|
38
|
-
|
|
39
|
-
None.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Medium Findings
|
|
44
|
-
|
|
45
|
-
### M1 — Missing Input Validation on `tableName` in `PostgresSchedulePersistence`
|
|
46
|
-
|
|
47
|
-
**File:** `src/scheduler/postgres-persistence.ts:153`
|
|
48
|
-
|
|
49
|
-
**Description:**
|
|
50
|
-
`PostgresSchedulePersistence` validates `schema` with a strict regex (`^[a-zA-Z_][a-zA-Z0-9_]{0,62}$`) but applies **no equivalent validation to `tableName`**. The raw `tableName` string is concatenated into `fullTableName` (`${this.schema}.${this.tableName}`) at line 235 and then passed to Kysely's `sql.table()` and `sql.ref()` at lines 257–296. While Kysely correctly double-quotes identifiers — preventing SQL injection — there is no upfront guard to reject nonsensical or adversarially crafted values.
|
|
51
|
-
|
|
52
|
-
Contrast: `SQLiteSchedulePersistence` (`src/scheduler/sqlite-persistence.ts:61`) validates its `tableName` with `^[a-zA-Z_][a-zA-Z0-9_]*$` and the Postgres *storage* adapter (`src/storage/postgres.ts:228`) validates its schema similarly. The omission in `postgres-persistence.ts` is an inconsistency in the defensive pattern.
|
|
53
|
-
|
|
54
|
-
**Impact:**
|
|
55
|
-
In the current implementation, `sql.table()` quoting prevents SQL injection. However, a consumer passing a malformed `tableName` (e.g. `"foo\x00bar"`, very long strings, or strings containing periods) will receive a confusing runtime database error rather than a clear upfront `Error`. If the Kysely dependency ever changes its quoting behaviour, this becomes a direct injection point.
|
|
56
|
-
|
|
57
|
-
**Remediation:**
|
|
58
|
-
Add the same regex guard used for `schema` immediately after line 153:
|
|
59
|
-
```typescript
|
|
60
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.tableName)) {
|
|
61
|
-
throw new Error(`Invalid table name "${this.tableName}".`);
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
### M2 — Non-Cryptographic ID Generation Used for Workflow Run Identifiers
|
|
68
|
-
|
|
69
|
-
**File:** `src/utils/id.ts:17`
|
|
70
|
-
|
|
71
|
-
**Description:**
|
|
72
|
-
`generateId()` constructs identifiers using `Date.now()` and `Math.random()`:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
`Math.random()` is not a CSPRNG. V8's implementation is seeded with a 64-bit value derived from the system clock at startup; an attacker with knowledge of the process start time can reconstruct the PRNG state and predict future IDs. These IDs are used as primary keys for workflow runs and steps (`src/core/orchestrator.ts`).
|
|
79
|
-
|
|
80
|
-
**Impact:**
|
|
81
|
-
If run IDs are used for access-control purposes by a consumer (e.g. "share a link containing the run ID" patterns), predictable IDs enable enumeration and unauthorized access. The library itself does not enforce any access control on run IDs, so impact depends entirely on the consuming application's threat model.
|
|
82
|
-
|
|
83
|
-
**Remediation:**
|
|
84
|
-
Replace `Math.random()` with `crypto.randomBytes` (Node) or `crypto.getRandomValues` (Web Crypto), both available in Node.js 18+:
|
|
85
|
-
```typescript
|
|
86
|
-
import { randomBytes } from 'node:crypto';
|
|
87
|
-
const randomPart = randomBytes(6).toString('base64url');
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Low Findings
|
|
93
|
-
|
|
94
|
-
### L1 — Blanket Block of All IPv6 Literals in SSRF Protection
|
|
95
|
-
|
|
96
|
-
**File:** `src/events/webhook.ts:493–496`
|
|
97
|
-
|
|
98
|
-
**Description:**
|
|
99
|
-
The `isBlockedHost` function blocks **all** IPv6 address literals, not just private/reserved ones:
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
// Block all IPv6 address literals (wrapped in brackets by URL parser)
|
|
103
|
-
if (hostname.startsWith('[')) {
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
This was added to prevent SSRF via `[::1]` and similar loopback bypasses (commit `48e5f0a`). While the intent is correct, the implementation also blocks legitimate public IPv6 targets such as `[2001:db8::1]`.
|
|
109
|
-
|
|
110
|
-
**Impact:**
|
|
111
|
-
Consumers cannot register webhook endpoints using public IPv6 addresses. This is a functional limitation, not a security vulnerability. As global IPv6 adoption grows, this will become increasingly restrictive.
|
|
112
|
-
|
|
113
|
-
**Remediation:**
|
|
114
|
-
Instead of blocking all bracket-wrapped addresses, parse the IPv6 address, expand it to full form, and check only against reserved ranges (loopback `::1`, ULA `fc00::/7`, link-local `fe80::/10`, documentation prefixes, etc.) using a library or the same range-check logic applied to IPv4.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
### L2 — `allowInsecureUrls` Flag Lacks Production-Safety Warning
|
|
119
|
-
|
|
120
|
-
**File:** `src/events/webhook.ts:64–67`
|
|
121
|
-
|
|
122
|
-
**Description:**
|
|
123
|
-
The `allowInsecureUrls` option on `WebhookEventTransportConfig` relaxes the HTTPS requirement and bypasses URL scheme validation:
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
/**
|
|
127
|
-
* Whether to allow non-HTTPS URLs (default: false).
|
|
128
|
-
* Set to true only in development environments.
|
|
129
|
-
*/
|
|
130
|
-
allowInsecureUrls?: boolean;
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
The comment mentions development use, but there is no runtime enforcement or warning when this flag is enabled in a context that looks like production (e.g. `NODE_ENV=production`).
|
|
134
|
-
|
|
135
|
-
**Impact:**
|
|
136
|
-
A consumer who copies a development configuration to production silently downgrades all webhooks to plain HTTP, exposing payloads (which may contain workflow results and metadata) to network interception, and disabling part of the SSRF protection.
|
|
137
|
-
|
|
138
|
-
**Remediation:**
|
|
139
|
-
Emit a logger warning at construction time when `allowInsecureUrls: true` is set, e.g.:
|
|
140
|
-
```typescript
|
|
141
|
-
if (this.allowInsecureUrls) {
|
|
142
|
-
this.logger.warn('allowInsecureUrls is enabled. Do not use this in production.');
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
### L3 — No Rate Limiting on Outbound Webhook Dispatch
|
|
149
|
-
|
|
150
|
-
**File:** `src/events/webhook.ts:465–480`
|
|
151
|
-
|
|
152
|
-
**Description:**
|
|
153
|
-
The webhook transport implements concurrency limiting (default 50 concurrent requests) and a queue, but no per-endpoint rate limiting or per-second emission cap. A burst of workflow events can cause hundreds of webhook deliveries in a short window.
|
|
154
|
-
|
|
155
|
-
**Impact:**
|
|
156
|
-
Could trigger rate limiting or bans from external webhook receivers. Does not directly introduce an inbound attack surface (the library sends, not receives), but could cause unintended denial-of-service toward third-party services.
|
|
157
|
-
|
|
158
|
-
**Remediation:**
|
|
159
|
-
Add an optional `rateLimit` configuration (e.g. `maxRequestsPerSecond`) per endpoint or globally, with token-bucket or leaky-bucket logic.
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Secrets & Credentials Exposure
|
|
164
|
-
|
|
165
|
-
**Result: None found.**
|
|
166
|
-
|
|
167
|
-
- `git grep` across all `*.ts`, `*.js`, `*.json`, `*.yaml`, `*.yml` files for `password`, `secret`, `api_key`, `token`, and `private_key` returned only:
|
|
168
|
-
- **`src/events/webhook.ts:22–23`** — `secret?: string` type definition for HMAC signing (no value)
|
|
169
|
-
- **`src/events/webhook.ts:116`** — JSDoc comment example: `secret: process.env.WEBHOOK_SECRET` (correctly references an environment variable, not a hardcoded value)
|
|
170
|
-
- **`src/planning/planner.ts`, `src/planning/types.ts`** — `tokensPerStep`, `tokens` — LLM token budget fields, not credentials
|
|
171
|
-
- **`dist/` build artefacts** — mirror of source, no embedded values
|
|
172
|
-
|
|
173
|
-
- No `.env` files exist in the repository at any path.
|
|
174
|
-
- `git log --all -p -- "*.env" "*.pem" "*.key"` returned no output — no credential files were ever committed to git history.
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Recommendations
|
|
179
|
-
|
|
180
|
-
The following are ordered by risk impact:
|
|
181
|
-
|
|
182
|
-
1. **[Medium — M1] Validate `tableName` in `PostgresSchedulePersistence`.**
|
|
183
|
-
Add a regex guard (`^[a-zA-Z_][a-zA-Z0-9_]{0,62}$`) for `tableName` at `src/scheduler/postgres-persistence.ts:153`, matching the existing pattern in `sqlite-persistence.ts:61` and `storage/postgres.ts:228`. This closes the inconsistency and provides a clean failure mode.
|
|
184
|
-
|
|
185
|
-
2. **[Medium — M2] Replace `Math.random()` with a CSPRNG in ID generation.**
|
|
186
|
-
Replace `Math.random()` at `src/utils/id.ts:17` with `crypto.getRandomValues` or Node's `crypto.randomBytes`. The timestamp prefix can remain. This is low-effort and eliminates predictability of workflow run IDs if consumers use them for access decisions.
|
|
187
|
-
|
|
188
|
-
3. **[Low — L2] Add a runtime warning for `allowInsecureUrls: true`.**
|
|
189
|
-
Log a warning through the configured logger in `WebhookEventTransport`'s constructor when `allowInsecureUrls` is set. This is a single-line addition that prevents silent production misconfiguration.
|
|
190
|
-
|
|
191
|
-
4. **[Low — L1] Narrow IPv6 blocking to reserved ranges only.**
|
|
192
|
-
Replace the blanket `hostname.startsWith('[')` block in `isBlockedHost` (`webhook.ts:493`) with targeted checks for `[::1]`, `[fc00:...]`, `[fe80:...]`, and other RFC-reserved IPv6 prefixes, allowing legitimate public IPv6 webhook endpoints.
|
|
193
|
-
|
|
194
|
-
5. **[Low — L3] Document or implement outbound webhook rate limiting.**
|
|
195
|
-
Either add an optional `rateLimit` configuration to `WebhookEventTransport`, or explicitly document in JSDoc that consumers are responsible for choosing receivers that can handle burst traffic.
|
|
196
|
-
|
|
197
|
-
6. **[Informational] Add explicit authorization enforcement documentation.**
|
|
198
|
-
The Socket.IO transport (`src/events/socketio.ts`) requires consumers to provide an `authorize` callback. Add JSDoc `@throws` documentation and an example showing what happens when no `authorize` callback is supplied, to prevent consumers from accidentally running with open subscriptions.
|
|
199
|
-
|
|
200
|
-
7. **[Informational] Consider documenting CORS requirements for Socket.IO consumers.**
|
|
201
|
-
The library accepts an already-constructed `SocketIOServer` (`src/events/socketio.ts:45`) and does not configure CORS. Add a README section or JSDoc example showing the minimum recommended CORS configuration to avoid consumers defaulting to wildcard origins.
|
|
202
|
-
|
|
203
|
-
8. **[Informational] Run `npm audit` in CI.**
|
|
204
|
-
The production dependency surface is minimal (only `cron-parser` and `re2`), but automated vulnerability scanning should be enforced in the CI pipeline to catch transitive dependency CVEs early.
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
*Audit performed: 2026-04-03. Branch: `main` (HEAD `4d9838a`). No source files were modified during this audit.*
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
# Security Audit: stepflow
|
|
2
|
-
|
|
3
|
-
**Date**: 2026-04-11T02:46:55.102Z
|
|
4
|
-
**Repository**: /srv/repos/stepflow
|
|
5
|
-
**Branch**: main
|
|
6
|
-
**Workflow Run**: 1376d4fe-4d4b-45f4-8663-781da416479e
|
|
7
|
-
**Session**: 20d039cc-df62-48da-a043-3b916b144149
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
Now I have all the information needed to produce the full security audit report.
|
|
12
|
-
|
|
13
|
-
## Security Audit: `@multiplier-labs/stepflow`
|
|
14
|
-
|
|
15
|
-
**Date:** 2026-04-11
|
|
16
|
-
**Repository:** `/srv/repos/stepflow`
|
|
17
|
-
**Branch:** `main` (HEAD `bd16961`)
|
|
18
|
-
**Tech Stack:** TypeScript / Node.js · better-sqlite3 · Kysely + PostgreSQL · Socket.IO (optional peer) · cron-parser · RE2
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Summary
|
|
23
|
-
|
|
24
|
-
**Overall Risk Rating: Low**
|
|
25
|
-
|
|
26
|
-
The codebase is a well-structured TypeScript workflow-orchestration library. Parameterized queries, SSRF protection with DNS-rebinding prevention, HMAC-SHA256 webhook signing, and a mandatory authorization callback on the Socket.IO transport demonstrate deliberate defence-in-depth. No hardcoded secrets or committed credential files were found. No critical or high vulnerabilities were discovered.
|
|
27
|
-
|
|
28
|
-
However, three medium findings from the prior audit (2026-04-03) remain **unaddressed** in `HEAD`:
|
|
29
|
-
|
|
30
|
-
| Severity | Count |
|
|
31
|
-
|----------|-------|
|
|
32
|
-
| Critical | 0 |
|
|
33
|
-
| High | 0 |
|
|
34
|
-
| Medium | 3 |
|
|
35
|
-
| Low | 3 |
|
|
36
|
-
| Informational | 3 |
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Critical Findings
|
|
41
|
-
|
|
42
|
-
None.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## High Findings
|
|
47
|
-
|
|
48
|
-
None.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Medium Findings
|
|
53
|
-
|
|
54
|
-
### M1 — Non-Cryptographic ID Generation for Workflow Run and Step Identifiers
|
|
55
|
-
|
|
56
|
-
**File:** `src/utils/id.ts:17`
|
|
57
|
-
|
|
58
|
-
**Description:**
|
|
59
|
-
`generateId()` constructs record primary keys using `Math.random()`:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
`Math.random()` is not a CSPRNG. V8's PRNG (`xorshift128+`) is seeded from system entropy at startup, but the full internal state can be recovered from a sequence of observed outputs. An attacker who can observe any generated IDs (e.g. via API responses, event payloads, or log lines) can reconstruct the PRNG state and predict past and future run IDs with high confidence.
|
|
66
|
-
|
|
67
|
-
These IDs are used as primary keys for all workflow runs (`src/core/orchestrator.ts`), steps (`src/storage/sqlite.ts`, `src/storage/postgres.ts`), schedules (`src/scheduler/cron.ts`), and events.
|
|
68
|
-
|
|
69
|
-
**Note:** This finding was identified in the 2026-04-03 audit and is still **unaddressed** in HEAD.
|
|
70
|
-
|
|
71
|
-
**Impact:** If a consumer treats run IDs as a capability token (e.g. embedding them in shareable URLs or using them to gate access), an attacker who can observe any prior run IDs can enumerate or predict run IDs they were never given, bypassing application-level access controls. Impact is entirely dependent on the consuming application's threat model, but the library cannot guarantee ID unpredictability for security-conscious consumers.
|
|
72
|
-
|
|
73
|
-
**Remediation:**
|
|
74
|
-
Replace `Math.random()` with a CSPRNG. Both options are available in Node.js 18+:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
// Option A – Node.js crypto (if running in Node)
|
|
78
|
-
import { randomBytes } from 'node:crypto';
|
|
79
|
-
export function generateId(): string {
|
|
80
|
-
const timestamp = Date.now().toString(36);
|
|
81
|
-
const randomPart = randomBytes(6).toString('base64url');
|
|
82
|
-
return `${timestamp}${randomPart}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Option B – Web Crypto (works in Node 18+ and browsers)
|
|
86
|
-
export function generateId(): string {
|
|
87
|
-
const timestamp = Date.now().toString(36);
|
|
88
|
-
const buf = new Uint8Array(6);
|
|
89
|
-
crypto.getRandomValues(buf);
|
|
90
|
-
const randomPart = Buffer.from(buf).toString('base64url');
|
|
91
|
-
return `${timestamp}${randomPart}`;
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
### M2 — Missing `tableName` Validation in `PostgresSchedulePersistence`
|
|
98
|
-
|
|
99
|
-
**File:** `src/scheduler/postgres-persistence.ts:153`
|
|
100
|
-
|
|
101
|
-
**Description:**
|
|
102
|
-
`PostgresSchedulePersistence` validates the `schema` configuration option with a strict regex at construction time:
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.schema)) {
|
|
106
|
-
throw new Error(`Invalid schema name "${this.schema}"...`);
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
However, `tableName` (line 153) receives **no equivalent validation**. The raw value is concatenated into `fullTableName` at line 234–235 and injected into DDL statements via `sql.table()` and `sql.ref()` at lines 257–299.
|
|
111
|
-
|
|
112
|
-
By contrast: `SQLiteSchedulePersistence` validates `tableName` with `^[a-zA-Z_][a-zA-Z0-9_]*$` at `src/scheduler/sqlite-persistence.ts:61`, and `PostgresStorageAdapter` validates `schema` at `src/storage/postgres.ts:228`. The omission in `postgres-persistence.ts` is an inconsistency in an otherwise consistent defensive pattern.
|
|
113
|
-
|
|
114
|
-
**Note:** This finding was identified in the 2026-04-03 audit and is still **unaddressed** in HEAD.
|
|
115
|
-
|
|
116
|
-
**Impact:** Kysely's `sql.table()` correctly double-quotes identifiers, so SQL injection is currently blocked by the ORM layer. However: (1) a consumer passing a malformed name receives an opaque database error instead of a clear early failure; (2) if Kysely's quoting is bypassed by any code path or future refactor, this becomes a direct DDL injection point; (3) the existing defensive pattern in the same file (for `schema`) creates a false sense of symmetry.
|
|
117
|
-
|
|
118
|
-
**Remediation:**
|
|
119
|
-
Add a regex guard immediately after line 153, matching the pattern already used for `schema`:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
this.tableName = config.tableName ?? 'workflow_schedules';
|
|
123
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.tableName)) {
|
|
124
|
-
throw new Error(
|
|
125
|
-
`Invalid table name "${this.tableName}". Table name must start with a letter or ` +
|
|
126
|
-
`underscore, contain only alphanumeric characters and underscores, and be at most 63 characters.`
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
### M3 — Stack Traces Included in `WorkflowError` Records Returned to Library Callers
|
|
134
|
-
|
|
135
|
-
**File:** `src/utils/errors.ts:28`, `src/utils/errors.ts:45`
|
|
136
|
-
|
|
137
|
-
**Description:**
|
|
138
|
-
`WorkflowEngineError.toRecord()` explicitly includes `stack: this.stack` in the serialized `WorkflowError`:
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
toRecord(): WorkflowError {
|
|
142
|
-
return {
|
|
143
|
-
code: this.code,
|
|
144
|
-
message: this.message,
|
|
145
|
-
stack: this.stack, // ← internal file paths exposed
|
|
146
|
-
details: this.details,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Similarly, `WorkflowEngineError.fromError()` (line 45) includes `stack: error.stack` when wrapping a generic `Error`. These `WorkflowError` records appear in `RunResult.error` (returned by `engine.startRun()`, `engine.waitForRun()`) and in the `WorkflowContext` passed to step handlers.
|
|
152
|
-
|
|
153
|
-
The library does apply `sanitizeErrorForStorage()` (which strips `stack`) before writing errors to the database. However, the in-memory `RunResult` returned synchronously to callers retains the full stack. If a consuming application forwards `RunResult` to an HTTP response body (a common pattern), the stack trace is exposed to external clients.
|
|
154
|
-
|
|
155
|
-
**Impact:** Stack traces expose absolute file-system paths, internal module names, and line numbers. This information aids an attacker in mapping the server's deployment layout and understanding application logic, reducing the effort required to exploit other vulnerabilities.
|
|
156
|
-
|
|
157
|
-
**Remediation:**
|
|
158
|
-
Strip the `stack` field from `WorkflowError` before it is surfaced via the public `RunResult` return value. This can be done in the `WorkflowError` type (mark `stack` as internal), in `toRecord()`, or in `executeWorkflow`'s error handling:
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
toRecord(): WorkflowError {
|
|
162
|
-
return {
|
|
163
|
-
code: this.code,
|
|
164
|
-
message: this.message,
|
|
165
|
-
// stack intentionally omitted from public records
|
|
166
|
-
details: this.details,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
If consumers need stack traces for server-side diagnostics, they can be logged via the `Logger` interface rather than included in the result object.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## Low Findings
|
|
176
|
-
|
|
177
|
-
### L1 — Blanket Block of All IPv6 Address Literals in SSRF Protection
|
|
178
|
-
|
|
179
|
-
**File:** `src/events/webhook.ts:493–496`
|
|
180
|
-
|
|
181
|
-
**Description:**
|
|
182
|
-
`isBlockedHost()` blocks **all** bracket-enclosed hostnames:
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
// Block all IPv6 address literals (wrapped in brackets by URL parser)
|
|
186
|
-
if (hostname.startsWith('[')) {
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
This blocks the loopback address `[::1]` as intended, but also blocks every legitimate public IPv6 address (e.g. `[2001:4860:4860::8888]`). The downstream `isBlockedIp()` function used for DNS-resolved addresses does contain scoped IPv6 checks, creating an inconsistency.
|
|
192
|
-
|
|
193
|
-
**Note:** This finding was identified in the 2026-04-03 audit and is still **unaddressed** in HEAD.
|
|
194
|
-
|
|
195
|
-
**Impact:** Functional limitation, not a security vulnerability. Consumers cannot register webhook endpoints at public IPv6 addresses. As IPv6 adoption grows, this will increasingly block legitimate integrations.
|
|
196
|
-
|
|
197
|
-
**Remediation:** Replace the blanket bracket-check with targeted checks for RFC-reserved IPv6 ranges: `::1` (loopback), `fc00::/7` (ULA), `fe80::/10` (link-local), and `::ffff:0:0/96` (IPv4-mapped). A minimal implementation:
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
if (hostname.startsWith('[')) {
|
|
201
|
-
const bare = hostname.slice(1, -1).toLowerCase();
|
|
202
|
-
if (bare === '::1' || bare.startsWith('fe80:') || bare.startsWith('fc') || bare.startsWith('fd')) {
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
// Allow other public IPv6 addresses
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
### L2 — `allowInsecureUrls: true` Provides No Runtime Warning
|
|
213
|
-
|
|
214
|
-
**File:** `src/events/webhook.ts:64–67`, `src/events/webhook.ts:145`
|
|
215
|
-
|
|
216
|
-
**Description:**
|
|
217
|
-
The `allowInsecureUrls` flag disables the HTTPS requirement and part of the SSRF scheme check. The JSDoc comment says "Set to true only in development environments," but there is no runtime enforcement or warning emitted via the configured logger when this flag is enabled.
|
|
218
|
-
|
|
219
|
-
**Impact:** A consumer who copies a development configuration to production silently downgrades all webhook deliveries to plain HTTP. Payload contents (which include workflow results, metadata, and run IDs) are then transmitted in cleartext and are susceptible to interception and replay attacks. The absence of a warning means this misconfiguration could persist undetected.
|
|
220
|
-
|
|
221
|
-
**Remediation:** Emit a warning at construction time when the flag is set:
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
if (this.allowInsecureUrls) {
|
|
225
|
-
this.logger.warn(
|
|
226
|
-
'WebhookEventTransport: allowInsecureUrls is enabled. ' +
|
|
227
|
-
'Webhook payloads will be sent over HTTP. Do not use this setting in production.'
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
### L3 — No Per-Endpoint Rate Limiting on Outbound Webhook Dispatch
|
|
235
|
-
|
|
236
|
-
**File:** `src/events/webhook.ts:465–480`
|
|
237
|
-
|
|
238
|
-
**Description:**
|
|
239
|
-
The webhook transport implements a global concurrency cap (`maxConcurrentRequests`, default 50) and a FIFO queue, but no per-endpoint or per-second emission rate limit. A burst of workflow events (e.g. completing 200 steps in rapid succession) can cause hundreds of webhook deliveries to a single endpoint within seconds.
|
|
240
|
-
|
|
241
|
-
**Impact:** Could trigger rate-limiting responses (HTTP 429) or IP bans from external webhook receivers, disrupting event delivery for all consumers sharing that endpoint. Does not introduce an inbound attack surface but could constitute inadvertent abuse of third-party services.
|
|
242
|
-
|
|
243
|
-
**Remediation:** Add an optional per-endpoint `rateLimit` configuration (requests per second) with token-bucket logic, or document explicitly in the API that callers are responsible for selecting receivers capable of handling burst traffic.
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## Secrets & Credentials Exposure
|
|
248
|
-
|
|
249
|
-
**Result: None found.**
|
|
250
|
-
|
|
251
|
-
Exhaustive `git grep` across all TypeScript, JavaScript, JSON, YAML, and environment file patterns for `password`, `secret`, `api_key`, `token`, and `private_key` found only:
|
|
252
|
-
|
|
253
|
-
| Location | Type | Assessment |
|
|
254
|
-
|---|---|---|
|
|
255
|
-
| `src/events/webhook.ts:22–23` | `secret?: string` type definition | Safe — no value |
|
|
256
|
-
| `src/events/webhook.ts:116` | JSDoc example: `process.env.WEBHOOK_SECRET` | Safe — env-var reference, not a literal |
|
|
257
|
-
| `src/planning/planner.ts`, `src/planning/types.ts` | `tokensPerStep`, `tokens` | LLM budget fields, not credentials |
|
|
258
|
-
| `dist/` build artifacts | Mirror of source | No embedded values |
|
|
259
|
-
|
|
260
|
-
No `.env` files exist in the repository. The full `git log` (`bd16961`…`0c78d90`) contains no commits that added credential files. No Base64-encoded or obfuscated secrets were detected.
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## Recommendations
|
|
265
|
-
|
|
266
|
-
Ordered by risk impact:
|
|
267
|
-
|
|
268
|
-
1. **[Medium — M1] Replace `Math.random()` with a CSPRNG in `src/utils/id.ts:17`.**
|
|
269
|
-
Use `crypto.getRandomValues()` or `crypto.randomBytes()`. This eliminates ID predictability for security-sensitive consumers and is a one-line change. Existing stored IDs are unaffected.
|
|
270
|
-
|
|
271
|
-
2. **[Medium — M2] Add `tableName` regex validation in `PostgresSchedulePersistence` constructor.**
|
|
272
|
-
Add `if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(this.tableName))` at `src/scheduler/postgres-persistence.ts:153`, matching the pattern already applied to `schema` in the same constructor and to `tableName` in `SQLiteSchedulePersistence`. This closes a defensive inconsistency and provides a clean failure mode.
|
|
273
|
-
|
|
274
|
-
3. **[Medium — M3] Strip `stack` from public `WorkflowError` records in `src/utils/errors.ts:28,45`.**
|
|
275
|
-
Remove `stack` from `toRecord()` and `fromError()` return values, or strip it at the `RunResult` construction site in the orchestrator. Log stack traces server-side via the `Logger` interface. This prevents internal paths leaking to callers who forward `RunResult` to HTTP responses.
|
|
276
|
-
|
|
277
|
-
4. **[Low — L2] Add a `logger.warn()` in `WebhookEventTransport` constructor when `allowInsecureUrls: true`.**
|
|
278
|
-
Single-line addition that prevents silent HTTP downgrade in production. Can be gated on `process.env.NODE_ENV === 'production'` if strictness is undesirable during integration tests.
|
|
279
|
-
|
|
280
|
-
5. **[Low — L1] Narrow IPv6 blocking to RFC-reserved ranges only in `isBlockedHost()`.**
|
|
281
|
-
Replace `hostname.startsWith('[')` at `webhook.ts:494` with targeted checks for `[::1]`, `[fe80:…]`, `[fc…]`, and `[fd…]` prefixes. Allows legitimate public IPv6 webhook endpoints without weakening SSRF protection.
|
|
282
|
-
|
|
283
|
-
6. **[Low — L3] Document or implement outbound webhook rate limiting.**
|
|
284
|
-
Either add an optional `rateLimit` option to `WebhookEventTransportConfig` with token-bucket semantics, or add explicit API documentation warning consumers about burst dispatch behaviour.
|
|
285
|
-
|
|
286
|
-
7. **[Informational] Add `@throws` JSDoc to `setupClientHandlers` documenting behavior when `authorize` returns `false`.**
|
|
287
|
-
Currently the silent `return` on denied subscriptions (socketio.ts:211, 235) is not documented in the public API. Clarify that a denied `workflow:subscribe` results in the socket not joining the room with no client-visible error, to prevent consumers from implementing broken "subscription failed" logic.
|
|
288
|
-
|
|
289
|
-
8. **[Informational] Document CORS configuration requirements for the Socket.IO transport.**
|
|
290
|
-
`SocketIOEventTransport` accepts an already-configured `SocketIOServer` and does not set CORS headers. Add a README example showing a minimal secure CORS configuration (`origin: ['https://app.example.com']` rather than `origin: '*'`) to prevent consumers from defaulting to wildcard origins.
|
|
291
|
-
|
|
292
|
-
9. **[Informational] Add `npm audit` to the CI pipeline.**
|
|
293
|
-
The production dependency surface is minimal (`cron-parser`, `re2`) but automated CVE scanning should run on every pull request. A GitHub Actions step of `npm audit --audit-level=high` would catch transitive vulnerabilities before they reach a release.
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
*Audit performed: 2026-04-11. Branch: `main` (HEAD `bd16961`). No source files were modified during this audit. Three findings (M1, M2, L1) were also present in the prior audit dated 2026-04-03 and remain unaddressed.*
|
package/.github/CODEOWNERS
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Default owners for all files
|
|
2
|
-
* @Multiplier-Labs/engineering
|
|
3
|
-
|
|
4
|
-
# CI/CD and GitHub configuration — require admin review
|
|
5
|
-
.github/ @Multiplier-Labs/admin
|
|
6
|
-
package.json @Multiplier-Labs/admin
|
|
7
|
-
package-lock.json @Multiplier-Labs/admin
|
|
8
|
-
|
|
9
|
-
# Security-sensitive code
|
|
10
|
-
src/events/webhook.ts @Multiplier-Labs/admin
|
|
11
|
-
src/utils/id.ts @Multiplier-Labs/admin
|
|
12
|
-
|
|
13
|
-
# Persistence layer
|
|
14
|
-
src/storage/ @Multiplier-Labs/engineering
|
|
15
|
-
src/scheduler/ @Multiplier-Labs/engineering
|
package/.github/dependabot.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
updates:
|
|
3
|
-
- package-ecosystem: npm
|
|
4
|
-
directory: /
|
|
5
|
-
schedule:
|
|
6
|
-
interval: weekly
|
|
7
|
-
day: monday
|
|
8
|
-
open-pull-requests-limit: 10
|
|
9
|
-
reviewers:
|
|
10
|
-
- Multiplier-Labs/engineering
|
|
11
|
-
labels:
|
|
12
|
-
- dependencies
|
|
13
|
-
|
|
14
|
-
- package-ecosystem: github-actions
|
|
15
|
-
directory: /
|
|
16
|
-
schedule:
|
|
17
|
-
interval: weekly
|
|
18
|
-
day: monday
|
|
19
|
-
open-pull-requests-limit: 5
|
|
20
|
-
reviewers:
|
|
21
|
-
- Multiplier-Labs/admin
|
|
22
|
-
labels:
|
|
23
|
-
- ci
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build-and-test:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
strategy:
|
|
16
|
-
matrix:
|
|
17
|
-
node-version: [20, 22]
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
20
|
-
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: ${{ matrix.node-version }}
|
|
23
|
-
cache: npm
|
|
24
|
-
|
|
25
|
-
- name: Install dependencies
|
|
26
|
-
run: npm ci
|
|
27
|
-
|
|
28
|
-
- name: Audit dependencies
|
|
29
|
-
run: npm audit --audit-level=high
|
|
30
|
-
|
|
31
|
-
- name: Typecheck
|
|
32
|
-
run: npm run typecheck
|
|
33
|
-
|
|
34
|
-
- name: Build
|
|
35
|
-
run: npm run build
|
|
36
|
-
|
|
37
|
-
- name: Test
|
|
38
|
-
run: npm test
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: Publish to GitHub Packages
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [created]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
publish:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
permissions:
|
|
11
|
-
contents: read
|
|
12
|
-
packages: write
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
15
|
-
|
|
16
|
-
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
17
|
-
with:
|
|
18
|
-
node-version: '20'
|
|
19
|
-
registry-url: 'https://registry.npmjs.org'
|
|
20
|
-
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
run: npm ci
|
|
23
|
-
|
|
24
|
-
- name: Audit dependencies
|
|
25
|
-
# Audit M3: a vulnerable dependency introduced between the last CI
|
|
26
|
-
# run and the release tag would otherwise ship to npm. Match the
|
|
27
|
-
# threshold used by ci.yml so the gate is consistent.
|
|
28
|
-
run: npm audit --audit-level=high
|
|
29
|
-
|
|
30
|
-
- name: Build
|
|
31
|
-
run: npm run build
|
|
32
|
-
|
|
33
|
-
- name: Typecheck
|
|
34
|
-
run: npm run typecheck
|
|
35
|
-
|
|
36
|
-
- name: Test
|
|
37
|
-
run: npm test
|
|
38
|
-
|
|
39
|
-
- name: Publish
|
|
40
|
-
run: npm publish
|
|
41
|
-
env:
|
|
42
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
name: Security
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
schedule:
|
|
9
|
-
- cron: '0 8 * * 1' # Weekly on Monday at 08:00 UTC
|
|
10
|
-
|
|
11
|
-
permissions:
|
|
12
|
-
contents: read
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
audit:
|
|
16
|
-
name: Dependency Audit
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
20
|
-
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: '20'
|
|
23
|
-
cache: npm
|
|
24
|
-
|
|
25
|
-
- name: Install dependencies
|
|
26
|
-
run: npm ci
|
|
27
|
-
|
|
28
|
-
- name: Run npm audit
|
|
29
|
-
run: npm audit --audit-level=moderate
|