@multiplier-labs/stepflow 0.3.3 → 0.3.4

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.
Files changed (69) hide show
  1. package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +146 -0
  2. package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
  3. package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
  4. package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
  5. package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
  6. package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
  7. package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
  8. package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
  9. package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
  10. package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
  11. package/.github/workflows/publish.yml +6 -0
  12. package/dist/{chunk-4VOKMGP6.js → chunk-5XJBAPXF.js} +517 -522
  13. package/dist/chunk-5XJBAPXF.js.map +1 -0
  14. package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
  15. package/dist/chunk-FMPJBK2N.js.map +1 -0
  16. package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
  17. package/dist/chunk-JPXPB7U5.js.map +1 -0
  18. package/dist/{chunk-GYPNJBDP.js → chunk-U3PCVK6O.js} +266 -47
  19. package/dist/chunk-U3PCVK6O.js.map +1 -0
  20. package/dist/events/index.d.ts +56 -3
  21. package/dist/events/index.js +1 -1
  22. package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
  23. package/dist/index.d.ts +2 -12
  24. package/dist/index.js +764 -485
  25. package/dist/index.js.map +1 -1
  26. package/dist/scheduler/index.d.ts +1 -1
  27. package/dist/scheduler/index.js +2 -2
  28. package/dist/storage/index.d.ts +313 -47
  29. package/dist/storage/index.js +4 -2
  30. package/docs/stepflow-api-reference.md +3 -1
  31. package/package.json +7 -4
  32. package/src/core/engine/concurrency.ts +229 -0
  33. package/src/core/engine/lifecycle.ts +176 -0
  34. package/src/core/engine.test.ts +70 -0
  35. package/src/core/engine.ts +91 -185
  36. package/src/core/orchestrator/persistence.ts +165 -0
  37. package/src/core/orchestrator/plan-resolution.ts +140 -0
  38. package/src/core/orchestrator/state-transitions.ts +109 -0
  39. package/src/core/orchestrator/step-runner.ts +427 -0
  40. package/src/core/orchestrator.ts +40 -359
  41. package/src/events/webhook.test.ts +292 -0
  42. package/src/events/webhook.ts +320 -77
  43. package/src/planning/evaluate-condition.test.ts +153 -0
  44. package/src/planning/evaluate-condition.ts +105 -0
  45. package/src/planning/planner.ts +1 -83
  46. package/src/planning/registry.ts +3 -85
  47. package/src/scheduler/cron.test.ts +9 -1
  48. package/src/scheduler/cron.ts +26 -2
  49. package/src/scheduler/postgres-persistence.ts +27 -15
  50. package/src/scheduler/sqlite-persistence.ts +28 -3
  51. package/src/storage/postgres-core.ts +550 -0
  52. package/src/storage/postgres-migration.test.ts +69 -0
  53. package/src/storage/postgres-transaction-pagination.test.ts +107 -0
  54. package/src/storage/postgres.ts +171 -690
  55. package/src/storage/sqlite-core.ts +151 -0
  56. package/src/storage/sqlite.test.ts +177 -1
  57. package/src/storage/sqlite.ts +183 -93
  58. package/src/utils/index.ts +2 -0
  59. package/src/utils/pinned-fetch.test.ts +84 -0
  60. package/src/utils/pinned-fetch.ts +118 -0
  61. package/src/utils/postgres-deps.ts +16 -0
  62. package/src/utils/safe-json.test.ts +166 -0
  63. package/src/utils/safe-json.ts +107 -0
  64. package/src/utils/webhook-signature.test.ts +111 -0
  65. package/src/utils/webhook-signature.ts +74 -0
  66. package/dist/chunk-4VOKMGP6.js.map +0 -1
  67. package/dist/chunk-GYPNJBDP.js.map +0 -1
  68. package/dist/chunk-KHTUWLVZ.js.map +0 -1
  69. package/dist/chunk-ML35PIHX.js.map +0 -1
@@ -0,0 +1,93 @@
1
+ # Dependency Health Report: stepflow
2
+
3
+ **Date**: 2026-04-11T02:46:13.072Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: 672a3272-e7d7-4127-b865-4e02a47eac94
7
+ **Session**: 0b582a44-067e-4ef0-b863-b0cd566a81fd
8
+
9
+ ---
10
+
11
+ Now I have all the data needed to compile the report.
12
+
13
+ ## Dependency Health Assessment — `@multiplier-labs/stepflow` v0.2.6
14
+
15
+ _Assessment date: 2026-04-11 | Package manager: **npm** | Lock file: `package-lock.json`_
16
+
17
+ ---
18
+
19
+ ## Summary
20
+
21
+ | Package Manager | Total Deps | Outdated (direct) | Vulnerabilities | Risk Level |
22
+ |---|---|---|---|---|
23
+ | npm | 296 (12 direct) | 7 | 4 (3 high, 1 moderate) | **HIGH** |
24
+
25
+ ---
26
+
27
+ ## Security Vulnerabilities
28
+
29
+ | Package | Severity | Advisory | Description | Fixed In |
30
+ |---|---|---|---|---|
31
+ | `kysely` | **HIGH** (CVSS 8.1) | GHSA-8cpq-38p9-67gx | SQL Injection via insufficient backslash escaping in `sql.lit()` and similar string-literal methods | `0.28.14` |
32
+ | `kysely` | **HIGH** (CVSS 8.1) | GHSA-fr9j-6mvq-frcv | SQL Injection via backslash escape bypass in non-type-safe JSON path key usage | `0.28.14` |
33
+ | `picomatch` | **HIGH** (CVSS 7.5) | GHSA-c2c7-rcm5-vvqj | ReDoS (Denial of Service) via extglob quantifiers — affects transitive dep from `vitest`/`chokidar` chain | `4.0.4` |
34
+ | `vite` | **HIGH** | GHSA-p9ff-h696-f583 | Arbitrary file read via Vite Dev Server WebSocket — transitive dep via `vitest` | `8.0.5` |
35
+ | `vite` | **HIGH** | GHSA-v2wj-q39q-566r | `server.fs.deny` access control bypass with query strings — transitive dep via `vitest` | `8.0.5` |
36
+ | `vite` | Moderate | GHSA-4w7w-66w2-5vf9 | Path traversal in optimized deps `.map` handling — transitive dep via `vitest` | `8.0.5` |
37
+ | `brace-expansion` | Moderate (CVSS 6.5) | GHSA-f886-m6hf-6m8v | Zero-step sequence causes process hang and memory exhaustion — transitive dep | `5.0.5` |
38
+ | `picomatch` | Moderate (CVSS 5.3) | GHSA-3v7f-55p6-f55p | Method injection in POSIX character classes causes incorrect glob matching | `4.0.4` |
39
+
40
+ > **Note:** `kysely` appears as both a `devDependency` and `peerDependency` in this project. The SQL injection issues affect MySQL dialect users specifically; SQLite and PostgreSQL dialects have lower exposure, but upgrading is still critical.
41
+
42
+ ---
43
+
44
+ ## Outdated Dependencies
45
+
46
+ | Package | Current | Latest | Gap | Type |
47
+ |---|---|---|---|---|
48
+ | `@types/node` | `22.19.5` | `25.6.0` | 3 major versions | devDependency |
49
+ | `typescript` | `5.9.3` | `6.0.2` | 1 major version | devDependency |
50
+ | `kysely` | `0.28.12` | `0.28.16` | 4 patches (**security**) | devDependency / peerDependency |
51
+ | `re2` | `1.23.3` | `1.24.0` | 1 minor | production dependency |
52
+ | `vitest` | `4.1.0` | `4.1.4` | 4 patches (transitive vuln fix) | devDependency |
53
+ | `@vitest/coverage-v8` | `4.1.0` | `4.1.4` | 4 patches | devDependency |
54
+ | `@types/pg` | `8.18.0` | `8.20.0` | 2 patches | devDependency |
55
+
56
+ ---
57
+
58
+ ## Abandoned / Unmaintained Packages
59
+
60
+ These are all **transitive dependencies** pulled in via the build/test toolchain (`tsup`, `prebuild-install`, `vitest`). Direct replacement is not possible without upstream action, but they represent long-term risk.
61
+
62
+ | Package | Installed Version | Est. Last Release | Dependency Path | Notes |
63
+ |---|---|---|---|---|
64
+ | `any-promise` | `1.3.0` | ~2016 (~10 yrs) | `tsup` → `mz` → `any-promise` | Effectively abandoned; superseded by native Promises |
65
+ | `object-assign` | `4.1.1` | ~2017 (~9 yrs) | `tsup` → build chain | Superseded by `Object.assign()` natively in Node.js |
66
+ | `file-uri-to-path` | `1.0.0` | ~2017 (~9 yrs) | `bindings` → `file-uri-to-path` | No longer needed for modern Node.js |
67
+ | `tunnel-agent` | `0.6.0` | ~2018 (~8 yrs) | `prebuild-install` → `request` chain | `request` itself is deprecated |
68
+ | `safe-buffer` | `5.2.1` | ~2020 (~6 yrs) | `readable-stream` → `safe-buffer` | Superseded by native `Buffer` APIs |
69
+ | `source-map` | `0.7.6` | ~2021 (~5 yrs) | `sucrase` → `source-map` | Minimal maintenance; `sucrase` is a dev-only tool |
70
+
71
+ ---
72
+
73
+ ## Recommendations
74
+
75
+ 1. **[Critical] Upgrade `kysely` to `≥0.28.14` immediately.** Two HIGH-severity SQL injection vulnerabilities (GHSA-8cpq-38p9-67gx, GHSA-fr9j-6mvq-frcv) affect all versions ≤0.28.13. Update both `devDependencies` and adjust `peerDependencies` range (`"kysely": ">=0.27.0"`) to document the minimum safe version. The fix is available at `0.28.16` (current latest).
76
+
77
+ 2. **[High] Upgrade `vitest` to `≥4.1.4` to pull in patched `vite` (≥8.0.5) and `picomatch` (≥4.0.4).** Three HIGH/moderate vulnerabilities in `vite` (GHSA-p9ff-h696-f583, GHSA-v2wj-q39q-566r, GHSA-4w7w-66w2-5vf9) and two in `picomatch` are fixed by bumping vitest. Although these only run in the dev server context, they pose a real risk in CI or development environments.
78
+
79
+ 3. **[High] Run `npm audit fix` to resolve `brace-expansion` (GHSA-f886-m6hf-6m8v).** This moderate ReDoS/memory-exhaustion vulnerability is in a transitive dep with a straightforward fix available. Running `npm audit fix` should resolve all 4 outstanding advisories once `kysely` and `vitest` are updated first.
80
+
81
+ 4. **[Medium] Upgrade `@types/node` from `22.x` to `24.x` (or `25.x`) in a planned sprint.** The installed version is 3 major versions behind. While `@types/node` bumps rarely break code, staying far behind obscures real type errors and misses new Node.js API typings. Align with the Node.js LTS version in use.
82
+
83
+ 5. **[Medium] Evaluate `TypeScript 6.x` migration.** The project uses `typescript ^5.7.2` while `6.0.2` is now the latest stable release. TypeScript 6 ships breaking changes (notably around module resolution and `erasableSyntaxOnly`). Plan a migration branch to assess impact on the codebase and tsup build config.
84
+
85
+ 6. **[Medium] Update `re2` to `1.24.0`.** `re2` is a **production dependency** — it directly affects the package published to consumers. Patch updates should be applied promptly; `1.24.0` is a minor bump with bug fixes.
86
+
87
+ 7. **[Low] Update peer dependency minimum for `kysely` to `>=0.28.14` in `package.json`.** Even if downstream users manage their own `kysely` install, advertising a range that includes vulnerable versions is irresponsible. Tightening the peer constraint signals the minimum safe version to consumers.
88
+
89
+ 8. **[Low] Audit and potentially remove `prebuild-install` → `tunnel-agent` chain.** `tunnel-agent` is pulled in via `re2`'s native build tooling (`prebuild-install`). Check whether `re2` 1.24.0 still ships prebuilt binaries for all target platforms; if so, `prebuild-install` (and its abandoned transitive deps) may not run at all in production.
90
+
91
+ 9. **[Low] Pin `vitest` and `@vitest/coverage-v8` to matching patch versions** (`4.1.4`). The `^4.1.0` range means CI will auto-install the latest patch, but local developer environments may lag. Lock to a specific version to ensure consistent test environments across machines.
92
+
93
+ 10. **[Informational] No production bundle contamination detected.** All dev dependencies (`vitest`, `tsup`, `typescript`, `@types/*`) are correctly scoped to `devDependencies` and excluded from the published package. The only two production dependencies are `cron-parser` and `re2`, which is appropriately lean for a library.
@@ -0,0 +1,138 @@
1
+ # Documentation Audit: stepflow
2
+
3
+ **Date**: 2026-04-11T02:47:05.901Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: 24ff56b6-9441-43f2-8563-c985920b774f
7
+ **Session**: ba7bd622-3564-476e-976b-21918677f818
8
+
9
+ ---
10
+
11
+ Now I have enough information to write the complete report.
12
+
13
+ ## Summary
14
+
15
+ **Audit date:** 2026-04-11 | **Repository:** `/srv/repos/stepflow` | **Branch:** `main`
16
+
17
+ The repository contains **3 documentation files** totalling **2,167 lines**. All three share the same commit timestamp (`2026-04-11 05:43:05`) from the initial import, so git-log staleness analysis is not applicable — all docs and code landed together. Cross-referencing documentation claims against the actual TypeScript source reveals the documentation is largely accurate and comprehensive, with **2 confirmed accuracy issues** and **1 minor fragmentation observation**.
18
+
19
+ **Health rating: Well-maintained** — minimal sprawl, no redundancy, nearly all claims verified against source.
20
+
21
+ ---
22
+
23
+ ## Documentation Inventory
24
+
25
+ | Path | Lines | Last Modified | Purpose | Status |
26
+ |------|-------|---------------|---------|--------|
27
+ | `README.md` | 408 | 2026-04-11 | Project overview, quick start, core concepts, feature tour, storage/event/scheduler options, planning system intro | Current |
28
+ | `docs/github-packages-setup.md` | 97 | 2026-04-11 | Publish and consume `@multiplier-labs/stepflow` via GitHub Packages; CI/CD token setup | Current |
29
+ | `docs/stepflow-api-reference.md` | 1,662 | 2026-04-11 | Full API reference: every class, interface, method signature, configuration option, PostgreSQL schema, integration patterns | Current (2 minor issues) |
30
+
31
+ ---
32
+
33
+ ## Staleness Findings
34
+
35
+ Because all documentation and source files share the same initial-commit timestamp, there is no temporal drift to report — no doc file predates its covered code or vice versa. No broken internal cross-links were found between the three files; the README's link to `docs/stepflow-api-reference.md` resolves correctly, and the GitHub Packages guide is self-contained.
36
+
37
+ No references to non-existent file paths, removed CLI commands, or deprecated config keys were found in any of the three documents.
38
+
39
+ ---
40
+
41
+ ## Accuracy Issues
42
+
43
+ ### Issue 1 — Non-existent `getSchedule()` method (confirmed)
44
+
45
+ **File:** `docs/stepflow-api-reference.md`
46
+ **Location:** Scheduler section (approximate line 499)
47
+ **Claim:** Documents a `scheduler.getSchedule(scheduleId: string): Promise<WorkflowSchedule>` method.
48
+ **Reality:** The `Scheduler` interface in `src/scheduler/types.ts` defines only `getSchedules(): Promise<WorkflowSchedule[]>` (plural, no `scheduleId` parameter). The singular `getSchedule(id)` does not exist on the interface.
49
+ **Impact:** Any developer who copies the shown usage will get a TypeScript compile error and runtime failure.
50
+
51
+ ### Issue 2 — `getInterruptedRuns()` method needs verification
52
+
53
+ **File:** `docs/stepflow-api-reference.md`
54
+ **Location:** Storage adapter section (approximate line 785)
55
+ **Claim:** References `storage.getInterruptedRuns()` on `SQLiteStorageAdapter`.
56
+ **Reality:** This method does not appear in the `StorageAdapter` interface (`src/storage/types.ts`) nor in the `WorkflowStorage` interface. It may be an undocumented implementation-only method, a leftover reference to a removed method, or a documentation invention. Requires reading `src/storage/sqlite.ts` to confirm.
57
+ **Impact:** Medium — if the method does not exist, documented usage will fail at compile time.
58
+
59
+ ### Everything Else — Verified Accurate
60
+
61
+ All of the following were cross-checked against source and match exactly:
62
+
63
+ - All exported names in `src/index.ts` match the TypeScript Types Reference section in the API reference.
64
+ - All `RunStatus`, `StepStatus`, `StepErrorStrategy` union literal values are correct.
65
+ - All `WorkflowContext`, `WorkflowStep`, `WorkflowHooks`, `WorkflowDefinition` interface properties and types are correct.
66
+ - All `StorageAdapter` and `WorkflowStorage` method signatures are correct.
67
+ - All 14 `BuiltInEventType` literals are correct.
68
+ - All `EventTransport` method signatures are correct.
69
+ - All `WorkflowSchedule` properties and `Scheduler` interface methods (except the `getSchedule` issue above) are correct.
70
+ - All 10 `ConditionOperator` values, all `Recipe`, `Plan`, `PlannedStep` interfaces are correct.
71
+ - All 9 error class names, error codes, and constructor signatures are correct.
72
+ - `WorkflowEngineConfig` and `StartRunOptions` interfaces are correct.
73
+ - `package.json` scripts (`build`, `dev`, `typecheck`, `test`, `test:watch`, `test:coverage`) match README claims.
74
+ - Peer dependency versions (`better-sqlite3 >=11.0.0`, `kysely >=0.27.0`, `pg >=8.13.0`) match `package.json`.
75
+ - Package name `@multiplier-labs/stepflow` and subpath exports (`/storage`, `/events`, `/scheduler`) are correct throughout all docs.
76
+
77
+ ---
78
+
79
+ ## Overlap & Redundancy
80
+
81
+ There is no meaningful overlap between the three files. Each occupies a distinct lane:
82
+
83
+ - `README.md` — conceptual overview and feature tour (prose-heavy, code snippets for motivation)
84
+ - `docs/github-packages-setup.md` — ops/distribution concern (PAT setup, CI/CD tokens, `npm version`)
85
+ - `docs/stepflow-api-reference.md` — exhaustive technical reference
86
+
87
+ Both `README.md` and `docs/stepflow-api-reference.md` contain a Quick Start section, but they use different examples (order-processing workflow vs. email-campaign workflow) and serve different readers (README = first-time evaluator, API ref = returning developer). This is a reasonable design choice, not a redundancy problem.
88
+
89
+ **No merge recommendations are warranted.**
90
+
91
+ ---
92
+
93
+ ## Fragmentation
94
+
95
+ With only 3 files, fragmentation is not a concern. The split between README and API reference follows widely accepted open-source convention. The GitHub Packages setup guide is appropriately separated as a distribution/ops concern that would clutter the main README.
96
+
97
+ No spec, proposal, or planning documents exist in the repo. No "completed feature" spec files need to be folded in or removed.
98
+
99
+ ---
100
+
101
+ ## Action Items
102
+
103
+ ### Delete
104
+
105
+ | File | Reason it's safe to delete |
106
+ |------|----------------------------|
107
+ | — | No deletion candidates. All three files serve distinct, active purposes. |
108
+
109
+ ### Consolidate
110
+
111
+ | Source Files | Target File | What to Keep / Drop |
112
+ |---|---|---|
113
+ | — | — | No consolidation needed. Files do not overlap in scope. |
114
+
115
+ ### Update
116
+
117
+ | File | Section Needing Update | What Changed |
118
+ |------|------------------------|--------------|
119
+ | `docs/stepflow-api-reference.md` | Scheduler section — `getSchedule(scheduleId)` method | Remove or replace with correct `getSchedules(): Promise<WorkflowSchedule[]>`. The documented method does not exist on the `Scheduler` interface. |
120
+ | `docs/stepflow-api-reference.md` | Storage section — `getInterruptedRuns()` on `SQLiteStorageAdapter` | Verify against `src/storage/sqlite.ts`. If the method no longer exists or was never part of the public interface, remove the reference. If it's an implementation detail not on the interface, add a note clarifying it is not part of the `StorageAdapter` contract. |
121
+
122
+ ---
123
+
124
+ ## Recommendations
125
+
126
+ 1. **Fix `getSchedule()` → `getSchedules()`** in `docs/stepflow-api-reference.md`. This is a confirmed broken API reference that will cause compile errors if followed. One-line fix.
127
+
128
+ 2. **Verify `getInterruptedRuns()`** by reading `src/storage/sqlite.ts`. If the method is absent, delete the reference. If present, ensure it is either promoted to the `StorageAdapter` interface (if intentionally public) or marked as an internal implementation detail.
129
+
130
+ 3. **Add a version changelog or `CHANGELOG.md`** if the package begins publishing new minor/major versions. Currently at `0.2.6`, with no changelog. As the API stabilises this becomes important for downstream consumers to understand what changed between releases.
131
+
132
+ 4. **Pin documented peer-dependency versions in the API reference.** The reference correctly lists minimum versions but does not mention tested/recommended ranges. As `better-sqlite3`, `kysely`, and `pg` release new majors, this becomes a source of drift. Consider a compatibility table or tested-with note.
133
+
134
+ 5. **Add a `CONTRIBUTING.md` or development-setup section** for contributors. The `package.json` has `dev`, `test`, `build`, and `typecheck` scripts, but there is no document explaining how to run the test suite locally, set up a Postgres instance for integration tests, or submit changes. This gap matters as the project gains external contributors.
135
+
136
+ 6. **Keep `docs/github-packages-setup.md` updated on PAT scope requirements.** GitHub periodically revises fine-grained PAT scopes. The current instructions reference `read:packages` — this is correct today, but is the most likely part of that document to go stale.
137
+
138
+ 7. **Consider lightweight doc-testing** (e.g., extracting TypeScript code blocks and running `tsc --noEmit` on them as part of CI). The one confirmed accuracy issue (`getSchedule`) would have been caught automatically if the Quick Start and integration examples were compiled as part of the build.
@@ -0,0 +1,121 @@
1
+ # Documentation Audit: stepflow
2
+
3
+ **Date**: 2026-04-13T01:17:34.517Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: 54339665-09b6-4e19-8981-c8fc3144a296
7
+ **Session**: 386a6c34-ce83-4c68-a8e7-e668435ced26
8
+
9
+ ---
10
+
11
+ Now I'll produce the final structured report based on the audit findings.
12
+
13
+ ## Summary
14
+
15
+ **Total documentation files:** 3 (2,167 lines total)
16
+ **Files needing attention:** 1 (minor — one missing error class entry)
17
+ **Health rating:** Well-maintained
18
+
19
+ The repository has a lean, focused documentation set: one root README, one GitHub Packages setup guide, and one comprehensive API reference. All three files share the same last-modified date (2026-04-11, the initial commit), so there is no divergence between doc age and code age. Code examples, configuration keys, export paths, and API signatures were cross-checked against the source and are accurate. The only finding is a single missing entry in the error-class table.
20
+
21
+ ---
22
+
23
+ ## Documentation Inventory
24
+
25
+ | Path | Lines | Last Modified | Purpose | Status |
26
+ |------|-------|--------------|---------|--------|
27
+ | `README.md` | 408 | 2026-04-11 | Project overview, quick-start, install, basic usage examples | Current |
28
+ | `docs/github-packages-setup.md` | 97 | 2026-04-11 | Instructions for authenticating with and publishing to GitHub Packages NPM registry | Current |
29
+ | `docs/stepflow-api-reference.md` | 1,662 | 2026-04-11 | Complete API reference: WorkflowEngine, storage adapters, event transports, scheduler, planner, error types, TypeScript types | Current (1 omission) |
30
+
31
+ ---
32
+
33
+ ## Staleness Findings
34
+
35
+ No staleness issues detected. All three documentation files were committed in the initial commit (2026-04-11) alongside the source code they describe. There is only one commit in the repository (`bd16961 Initial commit`), so no code has changed after the docs were written.
36
+
37
+ No broken internal links were found:
38
+ - `README.md` references `docs/github-packages-setup.md` — file exists.
39
+ - `README.md` references `docs/stepflow-api-reference.md` — file exists.
40
+ - All markdown anchor links (`#error-classes`, `#workflow-engine`, etc.) resolve to sections present in their respective files.
41
+
42
+ ---
43
+
44
+ ## Accuracy Issues
45
+
46
+ **Issue 1 — Missing error class in API Reference**
47
+
48
+ `WaitForRunTimeoutError` is exported from `src/index.ts` and implemented in `src/utils/errors.ts`, but it does not appear in the error-class table in `docs/stepflow-api-reference.md` (the table covers eight error types; this is the ninth).
49
+
50
+ - **Exported symbol:** `WaitForRunTimeoutError` with code `WAIT_FOR_RUN_TIMEOUT`, constructor signature `(runId, timeoutMs)`.
51
+ - **Where it is thrown:** `WorkflowEngine.waitForRun()` when the timeout elapses before the run completes.
52
+ - **Impact:** Users who encounter this error and search the API reference will not find it documented.
53
+
54
+ **Issue 2 — Storage adapter naming ambiguity (very minor)**
55
+
56
+ The TypeScript types section mentions both `StorageAdapter` and `WorkflowStorage` interfaces without explaining that `WorkflowStorage` is the extended/current interface while `StorageAdapter` is the narrower legacy surface. This distinction is inferable from the source but is not stated in the docs.
57
+
58
+ No issues were found with:
59
+ - Install / setup instructions (match `package.json` scripts and peer deps).
60
+ - Subpath export paths (`/storage`, `/events`, `/scheduler`) — all match `package.json` `exports` field.
61
+ - Code examples for `WorkflowEngine`, storage adapters, event transports, scheduler, and planner.
62
+ - All nine documented config keys for `WorkflowEngineConfig`, `PostgresStorageConfig`, and `SQLiteStorageConfig`.
63
+
64
+ ---
65
+
66
+ ## Overlap & Redundancy
67
+
68
+ No meaningful overlap exists across the three files. Each file occupies a distinct niche:
69
+
70
+ - `README.md` — orientation and quick-start; deliberately high-level.
71
+ - `docs/github-packages-setup.md` — operational runbook for package publishing; not replicated elsewhere.
72
+ - `docs/stepflow-api-reference.md` — exhaustive API detail; README does not duplicate it.
73
+
74
+ The README links to the API reference rather than reproducing its content, which is the correct pattern.
75
+
76
+ **No merge or deletion actions are warranted on overlap grounds.**
77
+
78
+ ---
79
+
80
+ ## Fragmentation
81
+
82
+ No fragmentation detected. The documentation set is already consolidated: three files covering three distinct concerns. There are no orphaned spec documents, proposal files, or feature-design docs in the repository. The `docs/` directory contains exactly two files, both purposeful and non-overlapping.
83
+
84
+ No "plan" or "proposal" documents describing completed work were found.
85
+
86
+ ---
87
+
88
+ ## Action Items
89
+
90
+ ### Delete
91
+
92
+ | File | Reason |
93
+ |------|--------|
94
+ | *(none)* | No files are outdated, superseded, or describe completed proposals with no reference value. |
95
+
96
+ ### Consolidate
97
+
98
+ | Source Files | Target File | Notes |
99
+ |-------------|-------------|-------|
100
+ | *(none)* | — | No consolidation needed; the current three-file structure is appropriate. |
101
+
102
+ ### Update
103
+
104
+ | File | Section Needing Update | What Changed |
105
+ |------|----------------------|--------------|
106
+ | `docs/stepflow-api-reference.md` | Error Classes table | Add missing `WaitForRunTimeoutError` entry: code `WAIT_FOR_RUN_TIMEOUT`, constructor `(runId, timeoutMs)`, thrown by `waitForRun()` on timeout. |
107
+ | `docs/stepflow-api-reference.md` | TypeScript Types Reference | Add a one-sentence note clarifying that `WorkflowStorage` is the current extended interface and `StorageAdapter` is the narrower legacy one, so users know which to implement. |
108
+
109
+ ---
110
+
111
+ ## Recommendations
112
+
113
+ 1. **Add `WaitForRunTimeoutError` to the error-class table** in `docs/stepflow-api-reference.md`. It is exported, thrown at a documented public API boundary (`waitForRun`), and its absence is the only real gap in API coverage.
114
+
115
+ 2. **Clarify `StorageAdapter` vs `WorkflowStorage`** in the TypeScript types section — a single sentence noting which is the current recommended interface and which is the narrower legacy surface will prevent confusion for implementors.
116
+
117
+ 3. **Add a `CHANGELOG.md` or version-history section** to `README.md` as the project evolves past v0.2.6. The current single-commit history makes this unnecessary now, but without it, future contributors will have no changelog to update when they introduce breaking changes.
118
+
119
+ 4. **Protect documentation accuracy through CI.** Because the repository has no docs-linting step, future code changes could silently invalidate examples. Consider adding a `docs:check` script (e.g., `markdownlint`, `markdown-link-check`) to the GitHub Actions workflow so broken links and formatting regressions are caught automatically.
120
+
121
+ 5. **Keep the three-file structure.** The current organisation (`README.md` + `docs/github-packages-setup.md` + `docs/stepflow-api-reference.md`) is clean and proportional to the codebase size. Resist splitting the API reference into per-module files until the reference exceeds ~3,000 lines or the docs audience clearly segments by module.
@@ -0,0 +1,297 @@
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.*