@mandujs/core 0.54.31 → 0.55.0-beta.1
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 +654 -654
- package/package.json +106 -210
- package/scripts/postinstall-lock.ts +153 -153
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +15 -15
- package/src/a11y/types.ts +125 -125
- package/src/agent/__tests__/apply.test.ts +224 -0
- package/src/agent/__tests__/context.test.ts +97 -98
- package/src/agent/apply.ts +1031 -0
- package/src/agent/context.ts +552 -552
- package/src/agent/index.ts +4 -3
- package/src/agent/plan.ts +161 -243
- package/src/agent/repair.ts +239 -162
- package/src/agent/sync.ts +197 -198
- package/src/agent/types.ts +242 -94
- package/src/agent/verify.ts +55 -55
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +15 -5
- package/src/auth/__tests__/reset.test.ts +2 -2
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +14 -2
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__snapshots__/build.test.ts.snap +5 -5
- package/src/bundler/__tests__/build-runner.ts +166 -166
- package/src/bundler/__tests__/client-boundary-transform.test.ts +524 -524
- package/src/bundler/__tests__/cold-start.test.ts +60 -60
- package/src/bundler/__tests__/css.test.ts +49 -20
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +24 -24
- package/src/bundler/__tests__/generation.test.ts +447 -0
- package/src/bundler/__tests__/hdr.test.ts +24 -18
- package/src/bundler/__tests__/hmr-client.test.ts +62 -24
- package/src/bundler/__tests__/jsx-runtime-shim.test.ts +179 -140
- package/src/bundler/__tests__/manifest-schema.test.ts +305 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +42 -42
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/analyzer.ts +15 -15
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +898 -915
- package/src/bundler/build.ts +113 -24
- package/src/bundler/client-boundary-transform.ts +977 -977
- package/src/bundler/css.ts +65 -44
- package/src/bundler/dev.ts +105 -73
- package/src/bundler/fast-refresh-preamble.ts +47 -47
- package/src/bundler/generation.ts +602 -0
- package/src/bundler/index.ts +3 -3
- package/src/bundler/manifest-schema.ts +55 -40
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +16 -15
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/prerender.ts +29 -9
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +1 -1
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +58 -56
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/props-serialization.test.ts +37 -37
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/island.ts +79 -79
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/props-serialization.ts +233 -233
- package/src/client/runtime-entry.ts +598 -598
- package/src/client/runtime.ts +1 -1
- package/src/client/serialize.ts +50 -50
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image-compat.ts +3 -0
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +1 -1
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +9 -9
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/generate-types.ts +1 -1
- package/src/content/index.ts +2 -2
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +9 -9
- package/src/db/index.ts +2 -2
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +82 -82
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/smoke.test.ts +2 -2
- package/src/desktop/__tests__/webview-fallback.test.ts +12 -32
- package/src/desktop/__tests__/window.test.ts +20 -99
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/webview-fallback.ts +17 -4
- package/src/desktop/window.ts +17 -8
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +1 -1
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/diagnose/__tests__/checks.test.ts +136 -136
- package/src/diagnose/checks.ts +185 -185
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +10 -10
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +5 -1
- package/src/filling/auth.ts +308 -308
- package/src/filling/body-parse.test.ts +60 -60
- package/src/filling/cookie-codec.ts +5 -3
- package/src/filling/deps.ts +265 -265
- package/src/filling/head-method.test.ts +154 -154
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.test.ts +48 -48
- package/src/generator/templates.ts +219 -219
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/check.ts +39 -24
- package/src/guard/config-guard.ts +13 -13
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/fs-routes-policy.ts +51 -51
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/index.ts +11 -11
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/__tests__/id.test.ts +3 -3
- package/src/id/index.ts +105 -105
- package/src/index.ts +9 -48
- package/src/internal/client-boundary.ts +266 -266
- package/src/internal/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -779
- package/src/kitchen/api/bundle-inspector.ts +179 -0
- package/src/kitchen/api/errors-grouping.ts +126 -126
- package/src/kitchen/api/file-api.ts +11 -11
- package/src/kitchen/kitchen-handler.ts +36 -0
- package/src/kitchen/kitchen-ui.ts +456 -0
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/perf/user-marks.ts +1 -1
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +7 -7
- package/src/resource/__tests__/schema.test.ts +14 -14
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +165 -165
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +146 -146
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/generator-schema.ts +11 -11
- package/src/resource/generators/slot.ts +72 -72
- package/src/resource/schema.ts +21 -21
- package/src/router/client-entry.test.ts +227 -227
- package/src/router/client-entry.ts +218 -218
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.test.ts +532 -532
- package/src/router/fs-routes.ts +53 -53
- package/src/router/fs-scanner.ts +216 -216
- package/src/router/fs-types.ts +19 -19
- package/src/router/route-source-analyzer.ts +521 -521
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/inline-client-hydration.test.ts +237 -237
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
- package/src/runtime/__tests__/page-render-response.test.ts +517 -517
- package/src/runtime/__tests__/request-middleware.test.ts +70 -70
- package/src/runtime/__tests__/searchparams-page-props.test.ts +81 -81
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/devtools-adapter.ts +68 -68
- package/src/runtime/escape.ts +34 -34
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/fast-refresh-types.ts +1 -1
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +30 -11
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/observability-lifecycle.ts +290 -290
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/page-render-response.ts +287 -274
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/rate-limit.ts +1 -1
- package/src/runtime/redirect.ts +1 -1
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/router.test.ts +4 -4
- package/src/runtime/router.ts +10 -10
- package/src/runtime/server.ts +39 -20
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +43 -17
- package/src/runtime/static-files.ts +369 -289
- package/src/runtime/streaming-ssr.ts +219 -180
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/__tests__/scheduler.test.ts +12 -11
- package/src/scheduler/index.ts +11 -5
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/spec/schema.ts +35 -35
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/index.ts +9 -3
- package/src/testing/lcov.ts +192 -0
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/__tests__/watcher.test.ts +59 -59
- package/src/watcher/watcher.ts +61 -61
|
@@ -1,286 +1,286 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 7.2 R1 Agent C (H3, L-01 + L-03) — URL length cap + slot path
|
|
3
|
-
* regex tests.
|
|
4
|
-
*
|
|
5
|
-
* Two independent hardenings, each with its own section:
|
|
6
|
-
*
|
|
7
|
-
* A — `fast-refresh-plugin#appendBoundary` — reject URLs that are
|
|
8
|
-
* over 2 KB long or contain characters that could escape an
|
|
9
|
-
* inline `<script>` context. The rejected file falls back to
|
|
10
|
-
* full-reload HMR with a console warning.
|
|
11
|
-
*
|
|
12
|
-
* B — `bundler/dev.ts` slot dispatch (line ~454) — reject
|
|
13
|
-
* `route.slotModule` paths that look like traversal, absolute
|
|
14
|
-
* paths, or otherwise violate the bundler's `.slot.ts(x)`
|
|
15
|
-
* convention. This is a `startDevBundler`-time check; we assert
|
|
16
|
-
* the behavior end-to-end via a real tempdir + valid manifest.
|
|
17
|
-
*
|
|
18
|
-
* References:
|
|
19
|
-
* docs/security/phase-7-1-audit.md §3 L-01 + L-03
|
|
20
|
-
* docs/bun/phase-7-2-team-plan.md §3 Agent C H3
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { describe, expect, test, afterAll, beforeAll } from "bun:test";
|
|
24
|
-
import { mkdtemp, mkdir, rm, writeFile } from "fs/promises";
|
|
25
|
-
import { tmpdir } from "os";
|
|
26
|
-
import path from "path";
|
|
27
|
-
import type { RoutesManifest } from "../../spec/schema";
|
|
28
|
-
import {
|
|
29
|
-
appendBoundary,
|
|
30
|
-
validateAcceptFileUrl,
|
|
31
|
-
MAX_ACCEPT_FILE_URL_LEN,
|
|
32
|
-
_testOnly_ALREADY_INJECTED,
|
|
33
|
-
} from "../fast-refresh-plugin";
|
|
34
|
-
import { startDevBundler, _testOnly_normalizeFsPath } from "../dev";
|
|
35
|
-
|
|
36
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
37
|
-
// Section A — URL length cap on appendBoundary / validateAcceptFileUrl
|
|
38
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
39
|
-
|
|
40
|
-
describe("appendBoundary — URL length cap + escape defense", () => {
|
|
41
|
-
test("[URL1] URL exactly at cap length is accepted", () => {
|
|
42
|
-
const url = "/" + "a".repeat(MAX_ACCEPT_FILE_URL_LEN - 1);
|
|
43
|
-
const v = validateAcceptFileUrl(url);
|
|
44
|
-
expect(v.ok).toBe(true);
|
|
45
|
-
const out = appendBoundary("export const x = 1;\n", url);
|
|
46
|
-
expect(out).toContain("window.__MANDU_HMR__");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("[URL2] URL one byte over cap is rejected with a reason", () => {
|
|
50
|
-
const url = "/" + "a".repeat(MAX_ACCEPT_FILE_URL_LEN);
|
|
51
|
-
const v = validateAcceptFileUrl(url);
|
|
52
|
-
expect(v.ok).toBe(false);
|
|
53
|
-
if (!v.ok) {
|
|
54
|
-
expect(v.reason).toMatch(/exceeds cap/);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("[URL3] URL with `</` substring is rejected (would break inline script context)", () => {
|
|
59
|
-
const url = "/app/evil</script>.client.tsx";
|
|
60
|
-
const v = validateAcceptFileUrl(url);
|
|
61
|
-
expect(v.ok).toBe(false);
|
|
62
|
-
if (!v.ok) expect(v.reason).toMatch(/unsafe substring/);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("[URL4] URL with newline is rejected", () => {
|
|
66
|
-
const url = "/app/path\nwith-newline.client.tsx";
|
|
67
|
-
const v = validateAcceptFileUrl(url);
|
|
68
|
-
expect(v.ok).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("[URL5] URL with <script substring is rejected", () => {
|
|
72
|
-
const url = "/app/<scripty>.client.tsx";
|
|
73
|
-
const v = validateAcceptFileUrl(url);
|
|
74
|
-
expect(v.ok).toBe(false);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("[URL6] appendBoundary on rejected URL returns source unchanged + no boundary", () => {
|
|
78
|
-
// Silence the console.warn during this expectation so test output stays clean.
|
|
79
|
-
const origWarn = console.warn;
|
|
80
|
-
let warnCalls = 0;
|
|
81
|
-
console.warn = (...args: unknown[]) => {
|
|
82
|
-
warnCalls += 1;
|
|
83
|
-
void args;
|
|
84
|
-
};
|
|
85
|
-
try {
|
|
86
|
-
const src = "export const x = 1;\n";
|
|
87
|
-
const out = appendBoundary(src, "/app/" + "a".repeat(MAX_ACCEPT_FILE_URL_LEN + 100) + ".client.tsx");
|
|
88
|
-
expect(out).toBe(src);
|
|
89
|
-
expect(out).not.toContain("__MANDU_HMR__");
|
|
90
|
-
expect(warnCalls).toBe(1);
|
|
91
|
-
} finally {
|
|
92
|
-
console.warn = origWarn;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("[URL7] short normal URL still accepted and emits acceptFile", () => {
|
|
97
|
-
const url = "/app/counter.client.tsx";
|
|
98
|
-
expect(validateAcceptFileUrl(url).ok).toBe(true);
|
|
99
|
-
const out = appendBoundary("export default function C() { return null; }\n", url);
|
|
100
|
-
expect(out).toContain('.acceptFile("/app/counter.client.tsx")');
|
|
101
|
-
// The idempotency guard survives: re-running appendBoundary is a no-op
|
|
102
|
-
const out2 = appendBoundary(out, url);
|
|
103
|
-
expect(out2).toBe(out);
|
|
104
|
-
expect(_testOnly_ALREADY_INJECTED.test(out)).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("[URL8] empty string URL rejected", () => {
|
|
108
|
-
expect(validateAcceptFileUrl("").ok).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("[URL9] non-string URL rejected (defense against buggy callsite)", () => {
|
|
112
|
-
// TS types forbid it, but appendBoundary is a pure fn — anyone could
|
|
113
|
-
// call it with a bad arg. Make sure validateAcceptFileUrl guards.
|
|
114
|
-
const v = validateAcceptFileUrl(123 as unknown as string);
|
|
115
|
-
expect(v.ok).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
120
|
-
// Section B — slotModule regex + traversal defense in startDevBundler
|
|
121
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
122
|
-
|
|
123
|
-
// These tests call into `startDevBundler` (which does an initial
|
|
124
|
-
// Bun.build). Skip under the bundler-flaky CI flag.
|
|
125
|
-
describe.skipIf(process.env.MANDU_SKIP_BUNDLER_TESTS === "1")(
|
|
126
|
-
"startDevBundler — slotModule path validation",
|
|
127
|
-
() => {
|
|
128
|
-
let rootDir = "";
|
|
129
|
-
|
|
130
|
-
beforeAll(async () => {
|
|
131
|
-
rootDir = await mkdtemp(path.join(tmpdir(), "mandu-slotmod-"));
|
|
132
|
-
// Minimal project so `buildClientBundles` does not explode.
|
|
133
|
-
await mkdir(path.join(rootDir, "app"), { recursive: true });
|
|
134
|
-
await mkdir(path.join(rootDir, "spec", "slots"), { recursive: true });
|
|
135
|
-
await writeFile(
|
|
136
|
-
path.join(rootDir, "app", "page.tsx"),
|
|
137
|
-
`export default function Page(){return null;}\n`,
|
|
138
|
-
"utf-8",
|
|
139
|
-
);
|
|
140
|
-
await writeFile(
|
|
141
|
-
path.join(rootDir, "spec", "slots", "home.slot.ts"),
|
|
142
|
-
`export async function load() { return {}; }\n`,
|
|
143
|
-
"utf-8",
|
|
144
|
-
);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
afterAll(async () => {
|
|
148
|
-
if (rootDir) await rm(rootDir, { recursive: true, force: true });
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("[SLOT1] valid slotModule path (spec/slots/*.slot.ts) is accepted — no warning", async () => {
|
|
152
|
-
const warnings: string[] = [];
|
|
153
|
-
const origWarn = console.warn;
|
|
154
|
-
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
155
|
-
try {
|
|
156
|
-
const manifest: RoutesManifest = {
|
|
157
|
-
version: 1,
|
|
158
|
-
routes: [
|
|
159
|
-
{
|
|
160
|
-
id: "home",
|
|
161
|
-
kind: "page",
|
|
162
|
-
pattern: "/",
|
|
163
|
-
module: "app/page.tsx",
|
|
164
|
-
componentModule: "app/page.tsx",
|
|
165
|
-
slotModule: "spec/slots/home.slot.ts",
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
};
|
|
169
|
-
const bundler = await startDevBundler({ rootDir, manifest });
|
|
170
|
-
bundler.close();
|
|
171
|
-
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
172
|
-
expect(slotWarnings).toEqual([]);
|
|
173
|
-
} finally {
|
|
174
|
-
console.warn = origWarn;
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("[SLOT2] path traversal slotModule (`../../etc/passwd`) is rejected with warning", async () => {
|
|
179
|
-
const warnings: string[] = [];
|
|
180
|
-
const origWarn = console.warn;
|
|
181
|
-
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
182
|
-
try {
|
|
183
|
-
const manifest: RoutesManifest = {
|
|
184
|
-
version: 1,
|
|
185
|
-
routes: [
|
|
186
|
-
{
|
|
187
|
-
id: "home",
|
|
188
|
-
kind: "page",
|
|
189
|
-
pattern: "/",
|
|
190
|
-
module: "app/page.tsx",
|
|
191
|
-
componentModule: "app/page.tsx",
|
|
192
|
-
slotModule: "../../../etc/passwd",
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
};
|
|
196
|
-
const bundler = await startDevBundler({ rootDir, manifest });
|
|
197
|
-
bundler.close();
|
|
198
|
-
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
199
|
-
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
200
|
-
} finally {
|
|
201
|
-
console.warn = origWarn;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("[SLOT3] absolute path slotModule is rejected", async () => {
|
|
206
|
-
const warnings: string[] = [];
|
|
207
|
-
const origWarn = console.warn;
|
|
208
|
-
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
209
|
-
try {
|
|
210
|
-
const manifest: RoutesManifest = {
|
|
211
|
-
version: 1,
|
|
212
|
-
routes: [
|
|
213
|
-
{
|
|
214
|
-
id: "home",
|
|
215
|
-
kind: "page",
|
|
216
|
-
pattern: "/",
|
|
217
|
-
module: "app/page.tsx",
|
|
218
|
-
componentModule: "app/page.tsx",
|
|
219
|
-
slotModule: "/etc/passwd",
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
};
|
|
223
|
-
const bundler = await startDevBundler({ rootDir, manifest });
|
|
224
|
-
bundler.close();
|
|
225
|
-
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
226
|
-
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
227
|
-
} finally {
|
|
228
|
-
console.warn = origWarn;
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("[SLOT4] Windows absolute path slotModule (C:\\...) is rejected", async () => {
|
|
233
|
-
const warnings: string[] = [];
|
|
234
|
-
const origWarn = console.warn;
|
|
235
|
-
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
236
|
-
try {
|
|
237
|
-
const manifest: RoutesManifest = {
|
|
238
|
-
version: 1,
|
|
239
|
-
routes: [
|
|
240
|
-
{
|
|
241
|
-
id: "home",
|
|
242
|
-
kind: "page",
|
|
243
|
-
pattern: "/",
|
|
244
|
-
module: "app/page.tsx",
|
|
245
|
-
componentModule: "app/page.tsx",
|
|
246
|
-
slotModule: "C:\\Windows\\System32\\drivers\\etc\\hosts",
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
};
|
|
250
|
-
const bundler = await startDevBundler({ rootDir, manifest });
|
|
251
|
-
bundler.close();
|
|
252
|
-
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
253
|
-
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
254
|
-
} finally {
|
|
255
|
-
console.warn = origWarn;
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
test("[SLOT5] wrong-extension slotModule (plain .ts not .slot.ts) is rejected", async () => {
|
|
260
|
-
const warnings: string[] = [];
|
|
261
|
-
const origWarn = console.warn;
|
|
262
|
-
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
263
|
-
try {
|
|
264
|
-
const manifest: RoutesManifest = {
|
|
265
|
-
version: 1,
|
|
266
|
-
routes: [
|
|
267
|
-
{
|
|
268
|
-
id: "home",
|
|
269
|
-
kind: "page",
|
|
270
|
-
pattern: "/",
|
|
271
|
-
module: "app/page.tsx",
|
|
272
|
-
componentModule: "app/page.tsx",
|
|
273
|
-
slotModule: "app/utils.ts",
|
|
274
|
-
},
|
|
275
|
-
],
|
|
276
|
-
};
|
|
277
|
-
const bundler = await startDevBundler({ rootDir, manifest });
|
|
278
|
-
bundler.close();
|
|
279
|
-
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
280
|
-
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
281
|
-
} finally {
|
|
282
|
-
console.warn = origWarn;
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
},
|
|
286
|
-
);
|
|
1
|
+
/**
|
|
2
|
+
* Phase 7.2 R1 Agent C (H3, L-01 + L-03) — URL length cap + slot path
|
|
3
|
+
* regex tests.
|
|
4
|
+
*
|
|
5
|
+
* Two independent hardenings, each with its own section:
|
|
6
|
+
*
|
|
7
|
+
* A — `fast-refresh-plugin#appendBoundary` — reject URLs that are
|
|
8
|
+
* over 2 KB long or contain characters that could escape an
|
|
9
|
+
* inline `<script>` context. The rejected file falls back to
|
|
10
|
+
* full-reload HMR with a console warning.
|
|
11
|
+
*
|
|
12
|
+
* B — `bundler/dev.ts` slot dispatch (line ~454) — reject
|
|
13
|
+
* `route.slotModule` paths that look like traversal, absolute
|
|
14
|
+
* paths, or otherwise violate the bundler's `.slot.ts(x)`
|
|
15
|
+
* convention. This is a `startDevBundler`-time check; we assert
|
|
16
|
+
* the behavior end-to-end via a real tempdir + valid manifest.
|
|
17
|
+
*
|
|
18
|
+
* References:
|
|
19
|
+
* docs/security/phase-7-1-audit.md §3 L-01 + L-03
|
|
20
|
+
* docs/bun/phase-7-2-team-plan.md §3 Agent C H3
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { describe, expect, test, afterAll, beforeAll } from "bun:test";
|
|
24
|
+
import { mkdtemp, mkdir, rm, writeFile } from "fs/promises";
|
|
25
|
+
import { tmpdir } from "os";
|
|
26
|
+
import path from "path";
|
|
27
|
+
import type { RoutesManifest } from "../../spec/schema";
|
|
28
|
+
import {
|
|
29
|
+
appendBoundary,
|
|
30
|
+
validateAcceptFileUrl,
|
|
31
|
+
MAX_ACCEPT_FILE_URL_LEN,
|
|
32
|
+
_testOnly_ALREADY_INJECTED,
|
|
33
|
+
} from "../fast-refresh-plugin";
|
|
34
|
+
import { startDevBundler, _testOnly_normalizeFsPath } from "../dev";
|
|
35
|
+
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
37
|
+
// Section A — URL length cap on appendBoundary / validateAcceptFileUrl
|
|
38
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
39
|
+
|
|
40
|
+
describe("appendBoundary — URL length cap + escape defense", () => {
|
|
41
|
+
test("[URL1] URL exactly at cap length is accepted", () => {
|
|
42
|
+
const url = "/" + "a".repeat(MAX_ACCEPT_FILE_URL_LEN - 1);
|
|
43
|
+
const v = validateAcceptFileUrl(url);
|
|
44
|
+
expect(v.ok).toBe(true);
|
|
45
|
+
const out = appendBoundary("export const x = 1;\n", url);
|
|
46
|
+
expect(out).toContain("window.__MANDU_HMR__");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("[URL2] URL one byte over cap is rejected with a reason", () => {
|
|
50
|
+
const url = "/" + "a".repeat(MAX_ACCEPT_FILE_URL_LEN);
|
|
51
|
+
const v = validateAcceptFileUrl(url);
|
|
52
|
+
expect(v.ok).toBe(false);
|
|
53
|
+
if (!v.ok) {
|
|
54
|
+
expect(v.reason).toMatch(/exceeds cap/);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("[URL3] URL with `</` substring is rejected (would break inline script context)", () => {
|
|
59
|
+
const url = "/app/evil</script>.client.tsx";
|
|
60
|
+
const v = validateAcceptFileUrl(url);
|
|
61
|
+
expect(v.ok).toBe(false);
|
|
62
|
+
if (!v.ok) expect(v.reason).toMatch(/unsafe substring/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("[URL4] URL with newline is rejected", () => {
|
|
66
|
+
const url = "/app/path\nwith-newline.client.tsx";
|
|
67
|
+
const v = validateAcceptFileUrl(url);
|
|
68
|
+
expect(v.ok).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("[URL5] URL with <script substring is rejected", () => {
|
|
72
|
+
const url = "/app/<scripty>.client.tsx";
|
|
73
|
+
const v = validateAcceptFileUrl(url);
|
|
74
|
+
expect(v.ok).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("[URL6] appendBoundary on rejected URL returns source unchanged + no boundary", () => {
|
|
78
|
+
// Silence the console.warn during this expectation so test output stays clean.
|
|
79
|
+
const origWarn = console.warn;
|
|
80
|
+
let warnCalls = 0;
|
|
81
|
+
console.warn = (...args: unknown[]) => {
|
|
82
|
+
warnCalls += 1;
|
|
83
|
+
void args;
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
const src = "export const x = 1;\n";
|
|
87
|
+
const out = appendBoundary(src, "/app/" + "a".repeat(MAX_ACCEPT_FILE_URL_LEN + 100) + ".client.tsx");
|
|
88
|
+
expect(out).toBe(src);
|
|
89
|
+
expect(out).not.toContain("__MANDU_HMR__");
|
|
90
|
+
expect(warnCalls).toBe(1);
|
|
91
|
+
} finally {
|
|
92
|
+
console.warn = origWarn;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("[URL7] short normal URL still accepted and emits acceptFile", () => {
|
|
97
|
+
const url = "/app/counter.client.tsx";
|
|
98
|
+
expect(validateAcceptFileUrl(url).ok).toBe(true);
|
|
99
|
+
const out = appendBoundary("export default function C() { return null; }\n", url);
|
|
100
|
+
expect(out).toContain('.acceptFile("/app/counter.client.tsx")');
|
|
101
|
+
// The idempotency guard survives: re-running appendBoundary is a no-op
|
|
102
|
+
const out2 = appendBoundary(out, url);
|
|
103
|
+
expect(out2).toBe(out);
|
|
104
|
+
expect(_testOnly_ALREADY_INJECTED.test(out)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("[URL8] empty string URL rejected", () => {
|
|
108
|
+
expect(validateAcceptFileUrl("").ok).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("[URL9] non-string URL rejected (defense against buggy callsite)", () => {
|
|
112
|
+
// TS types forbid it, but appendBoundary is a pure fn — anyone could
|
|
113
|
+
// call it with a bad arg. Make sure validateAcceptFileUrl guards.
|
|
114
|
+
const v = validateAcceptFileUrl(123 as unknown as string);
|
|
115
|
+
expect(v.ok).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
120
|
+
// Section B — slotModule regex + traversal defense in startDevBundler
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
122
|
+
|
|
123
|
+
// These tests call into `startDevBundler` (which does an initial
|
|
124
|
+
// Bun.build). Skip under the bundler-flaky CI flag.
|
|
125
|
+
describe.skipIf(process.env.MANDU_SKIP_BUNDLER_TESTS === "1")(
|
|
126
|
+
"startDevBundler — slotModule path validation",
|
|
127
|
+
() => {
|
|
128
|
+
let rootDir = "";
|
|
129
|
+
|
|
130
|
+
beforeAll(async () => {
|
|
131
|
+
rootDir = await mkdtemp(path.join(tmpdir(), "mandu-slotmod-"));
|
|
132
|
+
// Minimal project so `buildClientBundles` does not explode.
|
|
133
|
+
await mkdir(path.join(rootDir, "app"), { recursive: true });
|
|
134
|
+
await mkdir(path.join(rootDir, "spec", "slots"), { recursive: true });
|
|
135
|
+
await writeFile(
|
|
136
|
+
path.join(rootDir, "app", "page.tsx"),
|
|
137
|
+
`export default function Page(){return null;}\n`,
|
|
138
|
+
"utf-8",
|
|
139
|
+
);
|
|
140
|
+
await writeFile(
|
|
141
|
+
path.join(rootDir, "spec", "slots", "home.slot.ts"),
|
|
142
|
+
`export async function load() { return {}; }\n`,
|
|
143
|
+
"utf-8",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
afterAll(async () => {
|
|
148
|
+
if (rootDir) await rm(rootDir, { recursive: true, force: true });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("[SLOT1] valid slotModule path (spec/slots/*.slot.ts) is accepted — no warning", async () => {
|
|
152
|
+
const warnings: string[] = [];
|
|
153
|
+
const origWarn = console.warn;
|
|
154
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
155
|
+
try {
|
|
156
|
+
const manifest: RoutesManifest = {
|
|
157
|
+
version: 1,
|
|
158
|
+
routes: [
|
|
159
|
+
{
|
|
160
|
+
id: "home",
|
|
161
|
+
kind: "page",
|
|
162
|
+
pattern: "/",
|
|
163
|
+
module: "app/page.tsx",
|
|
164
|
+
componentModule: "app/page.tsx",
|
|
165
|
+
slotModule: "spec/slots/home.slot.ts",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
const bundler = await startDevBundler({ rootDir, manifest });
|
|
170
|
+
bundler.close();
|
|
171
|
+
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
172
|
+
expect(slotWarnings).toEqual([]);
|
|
173
|
+
} finally {
|
|
174
|
+
console.warn = origWarn;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("[SLOT2] path traversal slotModule (`../../etc/passwd`) is rejected with warning", async () => {
|
|
179
|
+
const warnings: string[] = [];
|
|
180
|
+
const origWarn = console.warn;
|
|
181
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
182
|
+
try {
|
|
183
|
+
const manifest: RoutesManifest = {
|
|
184
|
+
version: 1,
|
|
185
|
+
routes: [
|
|
186
|
+
{
|
|
187
|
+
id: "home",
|
|
188
|
+
kind: "page",
|
|
189
|
+
pattern: "/",
|
|
190
|
+
module: "app/page.tsx",
|
|
191
|
+
componentModule: "app/page.tsx",
|
|
192
|
+
slotModule: "../../../etc/passwd",
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
const bundler = await startDevBundler({ rootDir, manifest });
|
|
197
|
+
bundler.close();
|
|
198
|
+
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
199
|
+
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
200
|
+
} finally {
|
|
201
|
+
console.warn = origWarn;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("[SLOT3] absolute path slotModule is rejected", async () => {
|
|
206
|
+
const warnings: string[] = [];
|
|
207
|
+
const origWarn = console.warn;
|
|
208
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
209
|
+
try {
|
|
210
|
+
const manifest: RoutesManifest = {
|
|
211
|
+
version: 1,
|
|
212
|
+
routes: [
|
|
213
|
+
{
|
|
214
|
+
id: "home",
|
|
215
|
+
kind: "page",
|
|
216
|
+
pattern: "/",
|
|
217
|
+
module: "app/page.tsx",
|
|
218
|
+
componentModule: "app/page.tsx",
|
|
219
|
+
slotModule: "/etc/passwd",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
const bundler = await startDevBundler({ rootDir, manifest });
|
|
224
|
+
bundler.close();
|
|
225
|
+
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
226
|
+
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
227
|
+
} finally {
|
|
228
|
+
console.warn = origWarn;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("[SLOT4] Windows absolute path slotModule (C:\\...) is rejected", async () => {
|
|
233
|
+
const warnings: string[] = [];
|
|
234
|
+
const origWarn = console.warn;
|
|
235
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
236
|
+
try {
|
|
237
|
+
const manifest: RoutesManifest = {
|
|
238
|
+
version: 1,
|
|
239
|
+
routes: [
|
|
240
|
+
{
|
|
241
|
+
id: "home",
|
|
242
|
+
kind: "page",
|
|
243
|
+
pattern: "/",
|
|
244
|
+
module: "app/page.tsx",
|
|
245
|
+
componentModule: "app/page.tsx",
|
|
246
|
+
slotModule: "C:\\Windows\\System32\\drivers\\etc\\hosts",
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
};
|
|
250
|
+
const bundler = await startDevBundler({ rootDir, manifest });
|
|
251
|
+
bundler.close();
|
|
252
|
+
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
253
|
+
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
254
|
+
} finally {
|
|
255
|
+
console.warn = origWarn;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("[SLOT5] wrong-extension slotModule (plain .ts not .slot.ts) is rejected", async () => {
|
|
260
|
+
const warnings: string[] = [];
|
|
261
|
+
const origWarn = console.warn;
|
|
262
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
263
|
+
try {
|
|
264
|
+
const manifest: RoutesManifest = {
|
|
265
|
+
version: 1,
|
|
266
|
+
routes: [
|
|
267
|
+
{
|
|
268
|
+
id: "home",
|
|
269
|
+
kind: "page",
|
|
270
|
+
pattern: "/",
|
|
271
|
+
module: "app/page.tsx",
|
|
272
|
+
componentModule: "app/page.tsx",
|
|
273
|
+
slotModule: "app/utils.ts",
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
const bundler = await startDevBundler({ rootDir, manifest });
|
|
278
|
+
bundler.close();
|
|
279
|
+
const slotWarnings = warnings.filter((w) => w.includes("slotModule rejected"));
|
|
280
|
+
expect(slotWarnings.length).toBeGreaterThan(0);
|
|
281
|
+
} finally {
|
|
282
|
+
console.warn = origWarn;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
);
|