caspian-utils 0.2.1 → 0.2.3
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/dist/docs/ai-validation-checklist.md +3 -1
- package/dist/docs/auth.md +36 -19
- package/dist/docs/core-runtime-map.md +3 -3
- package/dist/docs/fetch-data.md +2 -1
- package/dist/docs/file-uploads.md +32 -5
- package/dist/docs/index.md +2 -1
- package/dist/docs/project-structure.md +26 -6
- package/dist/docs/static-export.md +1 -1
- package/package.json +1 -1
|
@@ -56,7 +56,9 @@ Use prompts like these to check whether AI lands on the correct docs and files.
|
|
|
56
56
|
| Make a grouped shell keep sidebar scroll while resetting page content on child-route navigation. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [core-runtime-map.md](./core-runtime-map.md) | `src/app/**/layout.html`, `public/js/pp-reactive-v2.js`, `main.py` | `pp-reset-scroll` placement, push-vs-history scroll behavior, and shared-shell ownership |
|
|
57
57
|
| Create a new contact page with interactive form behavior. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `src/app/**/index.html`, `main.py`, `.venv/Lib/site-packages/casp/components_compiler.py`, `.venv/Lib/site-packages/casp/scripts_type.py` | single-root template shape, script-inside-root authoring, and authored-vs-runtime boundaries |
|
|
58
58
|
| Add a button, filter, or form interaction to a route template. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) | `src/app/**/index.html`, `public/js/pp-reactive-v2.js`, `main.py` | PulsePoint `on*` event attributes, `pp.state`, directives, `Object.fromEntries(new FormData(...).entries())` for simple form submits, and avoiding id-driven `querySelector` or `addEventListener` wiring |
|
|
59
|
-
| Add a file manager page with upload progress and persisted metadata. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md), [database.md](./database.md) | `src/app/**/index.html`, `src/app/**/index.py`, `src/lib/**`, `prisma/schema.prisma` when Prisma applies | route-owned upload actions, persisted metadata flow, and mandatory Prisma Python ORM use for database reads and writes when Prisma is enabled |
|
|
59
|
+
| Add a file manager page with upload progress and persisted metadata. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md), [database.md](./database.md) | `src/app/**/index.html`, `src/app/**/index.py`, `src/lib/**`, `prisma/schema.prisma` when Prisma applies | route-owned upload actions, persisted metadata flow, and mandatory Prisma Python ORM use for database reads and writes when Prisma is enabled |
|
|
60
|
+
| Add `public/icons/app.png` and make it browser-accessible without editing routes. | [index.md](./index.md), [project-structure.md](./project-structure.md), [core-runtime-map.md](./core-runtime-map.md), [auth.md](./auth.md) | `public/**`, `main.py`, `.venv/Lib/site-packages/casp/runtime_security.py` | root-relative URL mapping, no per-directory route or prefix registry, traversal and symlink containment, `GET`/`HEAD` only, and middleware order |
|
|
61
|
+
| Store untrusted uploads in a custom public directory. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [project-structure.md](./project-structure.md), [core-runtime-map.md](./core-runtime-map.md) | `src/app/**/index.py`, `src/lib/**`, `main.py`, `.venv/Lib/site-packages/casp/runtime_security.py` | top-level `inline_safe_subdirectories` registration, attachment mode for HTML/SVG/unknown types, `nosniff`, path containment, and BrowserSync ignore rules |
|
|
60
62
|
| Explain why authored Caspian templates use a plain `<script>` instead of `type="text/pp"`. | [index.md](./index.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md), [routing.md](./routing.md) | `main.py`, `.venv/Lib/site-packages/casp/scripts_type.py`, `.venv/Lib/site-packages/casp/components_compiler.py` | script rewriting, `pp-component` injection, and authored-vs-runtime boundaries |
|
|
61
63
|
| Debug why `StateManager` does not persist across a full redirect. | [index.md](./index.md), [state.md](./state.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/state_manager.py` | wire vs non-wire reset behavior and `request.state.session` dependency |
|
|
62
64
|
| Trace the auth cookie and CSRF cookie names used in development. | [index.md](./index.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/auth.py` | development cookie scoping and CSRF or session naming ownership |
|
package/dist/docs/auth.md
CHANGED
|
@@ -291,35 +291,52 @@ The current `main.py` adds middleware in this source order:
|
|
|
291
291
|
```python
|
|
292
292
|
app.add_middleware(RPCMiddleware)
|
|
293
293
|
app.add_middleware(AuthMiddleware)
|
|
294
|
-
app.add_middleware(CSRFMiddleware)
|
|
295
|
-
app.add_middleware(SessionMiddleware, ...)
|
|
296
|
-
app.add_middleware(
|
|
294
|
+
app.add_middleware(CSRFMiddleware)
|
|
295
|
+
app.add_middleware(SessionMiddleware, ...)
|
|
296
|
+
app.add_middleware(BodySizeLimitMiddleware)
|
|
297
|
+
app.add_middleware(RateLimitMiddleware)
|
|
298
|
+
app.add_middleware(
|
|
299
|
+
PublicFilesMiddleware,
|
|
300
|
+
directory="public",
|
|
301
|
+
inline_safe_subdirectories={
|
|
302
|
+
"uploads": INLINE_SAFE_UPLOAD_MEDIA_TYPES,
|
|
303
|
+
},
|
|
304
|
+
)
|
|
305
|
+
app.add_middleware(SecurityHeadersMiddleware)
|
|
306
|
+
# RequestDiagnosticsMiddleware is added last outside production.
|
|
297
307
|
```
|
|
298
308
|
|
|
299
309
|
Because Starlette runs the last-added middleware first, the effective request order is:
|
|
300
310
|
|
|
301
|
-
1. `
|
|
302
|
-
2. `
|
|
303
|
-
3. `
|
|
304
|
-
4. `
|
|
305
|
-
5. `
|
|
306
|
-
6.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
311
|
+
1. `RequestDiagnosticsMiddleware` outside production
|
|
312
|
+
2. `SecurityHeadersMiddleware`
|
|
313
|
+
3. `PublicFilesMiddleware`
|
|
314
|
+
4. `RateLimitMiddleware`
|
|
315
|
+
5. `BodySizeLimitMiddleware`
|
|
316
|
+
6. `SessionMiddleware`
|
|
317
|
+
7. `CSRFMiddleware`
|
|
318
|
+
8. `AuthMiddleware`
|
|
319
|
+
9. `RPCMiddleware`
|
|
320
|
+
10. route handler or RPC endpoint
|
|
321
|
+
|
|
322
|
+
Current behavior by layer:
|
|
323
|
+
|
|
324
|
+
- `SecurityHeadersMiddleware` attaches baseline response headers from `casp.runtime_security`, including the Content-Security-Policy, `X-Content-Type-Options`, framing policy, referrer policy, permissions policy, and production HSTS, while preserving headers already set by the response. `CONTENT_SECURITY_POLICY` replaces the default CSP wholesale when the app needs a different policy.
|
|
325
|
+
- `PublicFilesMiddleware` serves any existing `GET`/`HEAD` file under `public/**` before rate limiting, sessions, CSRF, auth, RPC, or page routing. It falls through for missing files and preserves restricted inline-media handling for configured upload directories.
|
|
326
|
+
- `RateLimitMiddleware` applies the page budget to requests that were not already served as existing public files; `/health` is explicitly exempt.
|
|
327
|
+
- `BodySizeLimitMiddleware` rejects oversized request bodies before session, auth, RPC, or route parsing.
|
|
328
|
+
- `SessionMiddleware` provides `request.session` for the rest of the stack.
|
|
312
329
|
- `CSRFMiddleware` ensures `request.session["csrf_token"]` exists and emits a scoped CSRF cookie based on `pp_csrf`, for example `pp_csrf_5091` in development or plain `pp_csrf` when no dev scope is active.
|
|
313
|
-
- `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks,
|
|
330
|
+
- `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks, and enforces public, auth, private, and role-based route redirects. Existing public files never reach it because `PublicFilesMiddleware` serves them first.
|
|
314
331
|
- `RPCMiddleware` handles `POST` requests with `X-PP-RPC: true` and forwards them to Caspian's RPC handler after auth and session setup are already available.
|
|
315
332
|
|
|
316
|
-
Keep `SessionMiddleware`
|
|
333
|
+
Keep `SessionMiddleware` outside CSRF, auth, and RPC so those inner layers can access `request.session`. Keep `PublicFilesMiddleware` outside rate limiting, body parsing, sessions, CSRF, auth, and RPC, but inside `SecurityHeadersMiddleware`, so public files avoid per-user work and cookies while retaining security headers.
|
|
317
334
|
|
|
318
335
|
## Current `AuthMiddleware` Flow
|
|
319
336
|
|
|
320
|
-
The pasted `main.py` auth middleware currently behaves like this:
|
|
321
|
-
|
|
322
|
-
-
|
|
337
|
+
The pasted `main.py` auth middleware currently behaves like this:
|
|
338
|
+
|
|
339
|
+
- receives no request for an existing public file because the outer `PublicFilesMiddleware` has already served it; missing paths continue through normal auth and routing behavior
|
|
323
340
|
- initializes request-bound auth state with `StateManager.init(request)` and `Auth.set_request(request)`
|
|
324
341
|
- runs OAuth provider signin and callback handling before public or private route checks
|
|
325
342
|
- lets public routes through immediately
|
|
@@ -39,10 +39,10 @@ Important current `main.py` behaviors AI should keep in mind:
|
|
|
39
39
|
- `register_single_route(...)` passes path params to `page()` as one positional dict, injects matching query params by name, and injects `request` by keyword when declared.
|
|
40
40
|
- The render pipeline is `transform_components(...)`, then `render_with_nested_layouts(...)`, then `transform_scripts(...)`.
|
|
41
41
|
- Route-level generators returned from `page()` are wrapped in `SSE(...)` before the response is sent.
|
|
42
|
-
- Safe public-file helpers live in `casp.runtime_security`
|
|
42
|
+
- Safe public-file helpers live in `casp.runtime_security`. `PublicFilesMiddleware` maps every existing nested file under `public/**` to the same root-relative URL without per-directory routes, rejects traversal and symlink escape, handles only `GET`/`HEAD`, and falls through when no file exists so page and mounted-app routing still works. User-upload directories must retain their restricted inline-media policy.
|
|
43
43
|
- Session middleware secrets may be resolved through `casp.runtime_security` so production can fail fast when `AUTH_SECRET` is missing or still on a default placeholder.
|
|
44
44
|
- Baseline response headers are built by `casp.runtime_security` and attached through `SecurityHeadersMiddleware`. That set now includes a Content-Security-Policy from `build_content_security_policy()`, which the `CONTENT_SECURITY_POLICY` environment variable replaces wholesale. The policy must keep `'unsafe-eval'` and `'unsafe-inline'` in `script-src`, because the PulsePoint runtime compiles templates with `new Function`; read the current helper before tightening it. Outside production the helper also adds loopback origins to `connect-src` so the BrowserSync live-reload client, which polls a different port than the proxied page, is not blocked; an environment override replaces that too.
|
|
45
|
-
- Middleware is added in source order as `RPCMiddleware`, `AuthMiddleware`, `CSRFMiddleware`, `SessionMiddleware`, `BodySizeLimitMiddleware`, `RateLimitMiddleware`, `SecurityHeadersMiddleware`, plus `RequestDiagnosticsMiddleware` outside production. The effective request order is reversed
|
|
45
|
+
- Middleware is added in source order as `RPCMiddleware`, `AuthMiddleware`, `CSRFMiddleware`, `SessionMiddleware`, `BodySizeLimitMiddleware`, `RateLimitMiddleware`, `PublicFilesMiddleware`, `SecurityHeadersMiddleware`, plus `RequestDiagnosticsMiddleware` outside production. The effective production request order is reversed: security headers, public files, rate limit, body limit, session, CSRF, auth, then RPC and routing. Development diagnostics are outermost. Existing public-file `GET`/`HEAD` requests stop at `PublicFilesMiddleware`; missing paths fall through. Verify the current list in `main.py` rather than trusting an older order.
|
|
46
46
|
- `public/js/pp-reactive-v2.js` saves scroll positions per history entry, resets window scroll on push navigation, and uses `pp-reset-scroll="true"` to opt specific containers or the whole body into reset behavior.
|
|
47
47
|
|
|
48
48
|
## Caspian Core Feature Map
|
|
@@ -112,7 +112,7 @@ Use these behavior checkpoints when AI needs the fastest verification path for a
|
|
|
112
112
|
|
|
113
113
|
| Runtime area | Verify these behaviors |
|
|
114
114
|
| --- | --- |
|
|
115
|
-
| `main.py` routing and request flow | route registration, path and query injection,
|
|
115
|
+
| `main.py` routing and request flow | route registration, path and query injection, generic public-root file handling and middleware placement, session-middleware wiring, response-header middleware, and exception rendering |
|
|
116
116
|
| `main.py` WebSockets | `cfg.websocket` route-registration gate, `@app.websocket(...)` paths, and the shared transport loop (message-size, idle-timeout, close codes, connection cleanup). Authorization is a single guard in `src/lib/websocket/websocket_security.py` (`authorize_websocket`) that runs the origin check before `accept()` and delegates auth to `Auth`. The HTTP middleware stack skips `scope["type"] == "websocket"`, so socket auth is enforced by that guard, not by `AuthMiddleware` |
|
|
117
117
|
| `public/js/pp-reactive-v2.js` browser runtime | SPA interception, history-vs-push scroll behavior, `pp-reset-scroll` semantics, and browser-side `pp.rpc()` behavior |
|
|
118
118
|
| `casp.auth` | auth settings, signin and signout flow, provider wiring, and page protection behavior |
|
package/dist/docs/fetch-data.md
CHANGED
|
@@ -325,7 +325,8 @@ Use this pattern for real file managers:
|
|
|
325
325
|
|
|
326
326
|
- Keep upload and delete actions in the owning route `index.py`, not in `main.py`.
|
|
327
327
|
- Use `page()` to render the initial manager payload.
|
|
328
|
-
- Store durable metadata in Prisma and store the browser-accessible blob separately under a public upload directory such as `public/uploads/`.
|
|
328
|
+
- Store durable metadata in Prisma and store the browser-accessible blob separately under a public upload directory such as `public/uploads/`.
|
|
329
|
+
- Before accepting untrusted uploads, verify that the directory's top-level public name is configured in `PublicFilesMiddleware.inline_safe_subdirectories`; the conventional `public/uploads/` location uses the `uploads` entry so unsafe HTML, SVG, and unknown types download instead of rendering inline.
|
|
329
330
|
- Create `public/uploads` on demand in the shared helper if the directory does not exist yet.
|
|
330
331
|
- Use `pp.state(...)` plus `pp-for` for the list UI instead of manual `innerHTML` writes.
|
|
331
332
|
- Add the public-root-relative directory name `uploads` to `PUBLIC_IGNORE_DIRS` in `settings/bs-config.ts` when `public/uploads/` is the active upload root so runtime uploads do not trigger BrowserSync reloads during `npm run dev`.
|
|
@@ -34,7 +34,8 @@ If the file manager lives inside a grouped subtree such as a dashboard, account
|
|
|
34
34
|
- Keep first-render file manager data in `page()` so the initial HTML already contains the current asset list and storage summary.
|
|
35
35
|
- Keep upload and delete actions in the route's `index.py`; do not move ordinary upload flows into `main.py`.
|
|
36
36
|
- Keep reusable file-manager helpers in `src/lib/`.
|
|
37
|
-
- Store uploaded blobs under a project-owned public directory such as `public/uploads/...` when the files should be browser-accessible.
|
|
37
|
+
- Store uploaded blobs under a project-owned public directory such as `public/uploads/...` when the files should be browser-accessible.
|
|
38
|
+
- Confirm that the directory's top-level public name is present in `PublicFilesMiddleware.inline_safe_subdirectories` before storing untrusted uploads there. The conventional `public/uploads/**` path uses the `uploads` mapping.
|
|
38
39
|
- Create the upload directory on demand in the shared helper when it does not exist yet; do not assume the folder is committed.
|
|
39
40
|
- Store durable metadata in Prisma, not in JSON manifests or ad hoc metadata files.
|
|
40
41
|
- Use `pp.state(...)` plus `pp-for` to render and update the file list from returned server payloads.
|
|
@@ -48,7 +49,7 @@ If the file manager lives inside a grouped subtree such as a dashboard, account
|
|
|
48
49
|
| Upload and delete `@rpc()` actions | `src/app/**/index.py` | Keep these route-local so they stay close to the owning page. |
|
|
49
50
|
| Shared storage, normalization, and persistence helpers | `src/lib/**` | Reuse helpers across routes without pushing route behavior into app bootstrap. |
|
|
50
51
|
| Upload metadata model | `prisma/schema.prisma` | Persist owner, file name, MIME type, path, size, collection, and timestamps in Prisma. |
|
|
51
|
-
| Browser-accessible uploaded blobs | `public/uploads/**` or another
|
|
52
|
+
| Browser-accessible uploaded blobs | `public/uploads/**` or another explicitly protected public directory | Keep the public path predictable and derived from stored metadata, create it on demand, and configure its top-level name in `PublicFilesMiddleware.inline_safe_subdirectories` before accepting untrusted files. |
|
|
52
53
|
| BrowserSync upload ignore | `settings/bs-config.ts` | Keep the active public upload directory in `PUBLIC_IGNORE_DIRS`. |
|
|
53
54
|
|
|
54
55
|
## Route Flow
|
|
@@ -167,7 +168,32 @@ Typical metadata fields include:
|
|
|
167
168
|
- size in bytes
|
|
168
169
|
- uploaded timestamp
|
|
169
170
|
|
|
170
|
-
##
|
|
171
|
+
## Public-Serving Security
|
|
172
|
+
|
|
173
|
+
Every existing file under `public/**` is reachable at the matching
|
|
174
|
+
root-relative URL without a directory-specific route. That is correct for
|
|
175
|
+
trusted first-party assets, but runtime uploads cross a different trust
|
|
176
|
+
boundary. In `main.py`, configure each top-level upload directory with the
|
|
177
|
+
allow-list used by `PublicFilesMiddleware`:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
app.add_middleware(
|
|
181
|
+
PublicFilesMiddleware,
|
|
182
|
+
directory="public",
|
|
183
|
+
inline_safe_subdirectories={
|
|
184
|
+
"uploads": INLINE_SAFE_UPLOAD_MEDIA_TYPES,
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The resolver rejects traversal and symlink escape. Within configured upload
|
|
190
|
+
directories, allow-listed raster image types render inline; HTML, SVG, unknown,
|
|
191
|
+
and other executable or unsafe types download as
|
|
192
|
+
`application/octet-stream` with `Content-Disposition: attachment` and
|
|
193
|
+
`X-Content-Type-Options: nosniff`. Do not place untrusted uploads in a different
|
|
194
|
+
top-level public directory until that directory is added to the mapping.
|
|
195
|
+
|
|
196
|
+
## BrowserSync And Uploaded Public Files
|
|
171
197
|
|
|
172
198
|
If runtime uploads write into `public/uploads/`, BrowserSync should ignore that directory during local development. Otherwise every upload can trigger a full browser reload.
|
|
173
199
|
|
|
@@ -187,7 +213,8 @@ const PUBLIC_IGNORE_DIRS = ["uploads"];
|
|
|
187
213
|
- Do not use JSON files under `storage/` as the active metadata store when Prisma is enabled.
|
|
188
214
|
- Do not treat `onUploadProgress` callbacks as the source of truth for the final asset list.
|
|
189
215
|
- Do not manually repaint the file list with `innerHTML` when PulsePoint state can own the list.
|
|
190
|
-
- Do not leave uploaded public directories out of BrowserSync ignore rules.
|
|
216
|
+
- Do not leave uploaded public directories out of BrowserSync ignore rules.
|
|
217
|
+
- Do not store untrusted runtime uploads in an unconfigured top-level public directory; generic first-party public files render inline.
|
|
191
218
|
|
|
192
219
|
## AI Retrieval Notes
|
|
193
220
|
|
|
@@ -195,6 +222,6 @@ const PUBLIC_IGNORE_DIRS = ["uploads"];
|
|
|
195
222
|
- Use `fetch-data.md` for the route-render versus RPC split.
|
|
196
223
|
- Use `database.md` when Prisma models or relations must change for upload metadata.
|
|
197
224
|
- Use `validation.md` for MIME, extension, and other boundary checks, then keep explicit size and auth checks in the owning RPC action.
|
|
198
|
-
- Use `project-structure.md` for placement rules, especially `src/app/` versus `src/lib/` and `public/uploads
|
|
225
|
+
- Use `project-structure.md` for placement rules, especially `src/app/` versus `src/lib/` and `public/uploads/`; verify public serving and the restricted-inline mapping in `main.py` plus `casp.runtime_security`.
|
|
199
226
|
- For grouped file-manager sections, follow the section layout pattern in [routing.md](./routing.md) and keep the upload page in a child route folder beneath the shared shell.
|
|
200
227
|
- Use `commands.md` and `settings/bs-config.ts` when uploads should not trigger BrowserSync reloads during `npm run dev`.
|
package/dist/docs/index.md
CHANGED
|
@@ -39,7 +39,8 @@ When generating or editing a Caspian app, treat these as the default choices unl
|
|
|
39
39
|
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
40
40
|
- When `caspian.config.json` has `websocket: true`, use app-owned FastAPI WebSocket endpoints only for long-lived bidirectional live channels; keep normal browser-triggered data work on RPC.
|
|
41
41
|
- When `caspian.config.json` has `prisma: true`, use the generated Prisma Python ORM from `src/lib/prisma/**` for Python-side database reads and writes. Do not invent a second fetch layer, raw driver wrapper, JSON active store, or browser-side data path that bypasses Prisma.
|
|
42
|
-
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
42
|
+
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
43
|
+
- Treat `public/` as a direct URL-root mapping: `public/icons/app.png` is `/icons/app.png` without a per-directory route. Use the restricted-inline mapping in `PublicFilesMiddleware` for every top-level directory that receives untrusted runtime uploads.
|
|
43
44
|
|
|
44
45
|
## AI Doc Shape
|
|
45
46
|
|
|
@@ -180,11 +180,24 @@ This folder contains your database model definitions in `schema.prisma` and any
|
|
|
180
180
|
|
|
181
181
|
### `public/`
|
|
182
182
|
|
|
183
|
-
Store static assets here, including images, fonts, and generated frontend assets that should be served directly.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
Store static assets here, including images, fonts, and generated frontend assets that should be served directly.
|
|
184
|
+
|
|
185
|
+
The public root maps directly to the URL root. Any existing nested file is
|
|
186
|
+
available at the same root-relative URL—for example,
|
|
187
|
+
`public/icons/app.png` is served as `/icons/app.png`. Adding a public
|
|
188
|
+
subdirectory does not require a matching route or mount in `main.py`.
|
|
189
|
+
|
|
190
|
+
Runtime-uploaded public blobs can also live here. Confirm the actual upload path in the project code, commonly `public/uploads/`, and keep that directory aligned with any BrowserSync ignore rules.
|
|
191
|
+
|
|
192
|
+
If the upload directory is created only at runtime, create it on demand in the owning upload helper instead of assuming it is already committed.
|
|
193
|
+
|
|
194
|
+
Public means browser-accessible, not automatically safe to render inline.
|
|
195
|
+
`PublicFilesMiddleware.inline_safe_subdirectories` must include every top-level
|
|
196
|
+
public directory that receives untrusted runtime uploads. Unsafe types in a
|
|
197
|
+
configured upload directory, notably HTML and SVG, are sent as attachments with
|
|
198
|
+
`nosniff`; trusted first-party assets are served inline. The standard
|
|
199
|
+
`public/uploads/**` location is protected only when `main.py` configures the
|
|
200
|
+
top-level `uploads` entry.
|
|
188
201
|
|
|
189
202
|
If the local BrowserSync stack is running, keep that upload directory in `settings/bs-config.ts` `PUBLIC_IGNORE_DIRS` so new uploads do not force a full browser reload.
|
|
190
203
|
|
|
@@ -218,7 +231,14 @@ Use this package module when the task is about:
|
|
|
218
231
|
- user-facing vs production-safe exception messaging helpers
|
|
219
232
|
- baseline response headers such as permissions, referrer, MIME sniffing, framing, or HSTS behavior
|
|
220
233
|
|
|
221
|
-
Because this file is framework-owned, edit it only for Caspian runtime work or when documentation must match the installed package. App-specific auth policy still belongs in `src/lib/auth/auth_config.py`, and app-specific upload or storage behavior should live in route-owned code or other `src/lib/**` helpers.
|
|
234
|
+
Because this file is framework-owned, edit it only for Caspian runtime work or when documentation must match the installed package. App-specific auth policy still belongs in `src/lib/auth/auth_config.py`, and app-specific upload or storage behavior should live in route-owned code or other `src/lib/**` helpers.
|
|
235
|
+
|
|
236
|
+
`PublicFilesMiddleware` serves only existing `GET`/`HEAD` files that resolve
|
|
237
|
+
inside the configured public root and falls through for every other request.
|
|
238
|
+
Install it inside the security-header layer and outside rate limiting, sessions,
|
|
239
|
+
CSRF, auth, RPC, and page routing. Preserve a restricted inline-media policy for
|
|
240
|
+
user-upload directories so executable uploads such as HTML and SVG download
|
|
241
|
+
instead of rendering with the application's origin.
|
|
222
242
|
|
|
223
243
|
### `caspian.config.json`
|
|
224
244
|
|
|
@@ -62,7 +62,7 @@ The script is composed so the export always runs against a **fresh route/compone
|
|
|
62
62
|
|
|
63
63
|
- boots the real ASGI app in-process and requests every route through Starlette's `TestClient`, so exported HTML is byte-identical to what the dev server serves (layouts, components, PulsePoint deferral, security headers — the whole pipeline),
|
|
64
64
|
- writes each page to `static/<route>/index.html` (`/` → `static/index.html`),
|
|
65
|
-
- mirrors public
|
|
65
|
+
- mirrors the complete `public/**` tree into `static/`, including app-defined directories such as `icons` and `fonts`,
|
|
66
66
|
- runs in `APP_ENV=development` so the build does not require production secrets.
|
|
67
67
|
|
|
68
68
|
**Scope policy is "warn & skip"** — anything that cannot be fully static is reported and NOT written, so nothing broken ships silently:
|