Skip to content

v1.2.1

Choose a tag to compare

@DominusKelvin DominusKelvin released this 27 Feb 10:36

inertia-sails v1.3.1

Security: Shared props no longer leak across requests

This release fixes a critical bug where user-specific shared props could leak between requests, causing one user's data (e.g., loggedInUser) to appear in another user's session — including unauthenticated visitors.

This affects all Boring Stack apps that call sails.inertia.share() in routes.before handlers, which is the standard pattern for passing logged-in user data to the frontend.

The bug

Three issues combined to create a data leak:

  1. share() silently fell back to global storage — When called outside AsyncLocalStorage context, share() wrote to globalSharedProps, a process-level singleton. That data persisted and bled into every subsequent request.

  2. flushShared() never cleaned global storage — The global parameter defaulted to false, so stale data in globalSharedProps was never cleared between requests.

  3. Hook load order created a race condition — inertia-sails set up AsyncLocalStorage context in routes.before, but other hooks (like the custom hook) also use routes.before to call share(). If another hook's handler ran first, share() was called before context existed, triggering the global fallback.

The fix

AsyncLocalStorage context is now injected as HTTP middleware during configure(), guaranteeing it exists before any hook's routes.before handler runs.

Request lifecycle (before):
  routes.before handlers (race condition!) → router → controller

Request lifecycle (after):
  cookieParser → session → ... → inertiaContext → routes.before handlers → router → controller
                                  ↑
                          AsyncLocalStorage context
                          is ready before any hook
                          calls share()

The fix uses Sails' configure() lifecycle phase — which runs for all hooks before any hook's initialize() — to inject an inertiaContext middleware into the HTTP stack before the router.

For socket requests (which bypass Express middleware and go through sails.router.route() directly), the existing routes.before handlers remain as a fallback with a guard that skips setup if context was already established by the HTTP middleware.

Breaking changes

share() no longer falls back to global storage

Previously, calling share() outside a request context silently wrote to globalSharedProps. Now it logs a warning and discards the value.

// This still works — called inside routes.before, controllers, helpers, etc.
sails.inertia.share('loggedInUser', user)

// This now logs a warning instead of silently leaking data
// Use shareGlobally() for truly global data like app name
sails.inertia.shareGlobally('appName', 'My App')

If you see warnings like share('key') called outside request context, move the call into a routes.before handler, controller, or helper — anywhere inside the request lifecycle.

flushShared() always cleans global storage

The global parameter has been removed. flushShared() now always cleans both request-scoped and global storage to prevent stale data.

// Before
sails.inertia.flushShared('key', true)  // had to pass true to clean global
sails.inertia.flushShared()             // only cleaned request-scoped

// After
sails.inertia.flushShared('key')        // cleans both request-scoped and global
sails.inertia.flushShared()             // cleans both request-scoped and global

local() no longer falls back to global storage

Same change as share() — use localGlobally() for global locals.

How to verify the fix

  1. Update inertia-sails:

    npm install inertia-sails@1.3.1
  2. Start your app and log in as User A in one browser

  3. Open the app in a different browser or incognito window (not logged in)

  4. Verify the unauthenticated browser does not show User A's avatar, name, or any session data

  5. Check server logs — there should be no share() called outside request context warnings under normal operation

Additional fixes

  • TypeScript: Fixed AsyncLocalStorage.run() callback type error — wrapped the callback to satisfy the () => any signature expected by the run() overload

References

Migration guide

No code changes required in your app. Just update the dependency:

npm install inertia-sails@1.3.1

If you were calling flushShared() with the second global argument, remove it — global is now always cleaned:

// Before
sails.inertia.flushShared('key', true)

// After
sails.inertia.flushShared('key')

Full Changelog: v1.2.0...v1.2.1