#1044·evershop

[BUG] EverShop Uses a Public Default Secret for Session Cookies

Author: 28HusCreated Sep 7, 2026Updated Sep 7, 2026

EverShop Uses a Public Default Secret for Session Cookies

Summary

EverShop uses the public value keyboard cat when system.session.cookieSecret is not configured. The value is used to sign EverShop session cookies and other signed cookies.

An attacker can therefore generate valid signatures for arbitrary cookie values. In the production authentication flow, however, the cookie contains only a session identifier. EverShop loads the session from PostgreSQL and loads the administrator from the server-side userID. The public secret alone does not create an authenticated administrator session.

This is a hard-coded default session secret. It becomes an authentication bypass when combined with a separate session-ID disclosure, session fixation, or attacker-controlled server-side session state.

Evidence

The public fallback is defined here:

typescript
export const getCookieSecret = (): string =>
  getConfig('system.session.cookieSecret', 'keyboard cat');

Source: getCookieSecret.ts

The value is passed to both express-session and cookie-parser:

typescript
const cookieSecret = getCookieSecret();

const sess = {
  store: new (sessionStorage(session))({ pool }),
  secret: cookieSecret,
  // ...
};

app.use(cookieParser(cookieSecret));

Source: addDefaultMiddlewareFuncs.ts

After login, EverShop stores the administrator ID in the server-side session:

typescript
if (this.session) {
  this.session.userID = user.admin_user_id;
}

Source: loginUserWithEmail.ts

For an admin request, EverShop uses the cookie as a Session ID, loads the corresponding PostgreSQL session, and then queries the active administrator using the stored userID:

typescript
const sessionID = cookies[adminSessionCookieName];
const adminSessionData = await getSession(sessionID);

currentAdminUser = await select()
  .from('admin_user')
  .where('admin_user_id', '=', adminSessionData.userID)
  .and('status', '=', 1)
  .load(pool);

Source: [context]getCurrentUser.ts

EverShop's JWT path uses separate configured secrets and does not fall back to keyboard cat:

typescript
if (!secret) {
  throw new Error(`JWT secret for ${tokenType} is not configured`);
}

Source: jwt.ts

Proof of concept

Run this only against a local EverShop test instance. The following creates a correctly signed admin-session cookie for an attacker-chosen Session ID:

javascript
const signature = require('cookie-signature');

const sessionId = 'attacker-chosen-session-id';
const signed = `s:${sessionId}.${signature.sign(sessionId, 'keyboard cat')}`;

console.log(encodeURIComponent(signed));

Send the result to a private admin route:

bash
curl -i \
  -H 'Cookie: asid=<url-encoded-signed-cookie>' \
  http://127.0.0.1:3000/api/user/session/tokens

Expected result: 401 Unauthorized. The signature is valid, but the chosen Session ID has no authenticated server-side session. This demonstrates the boundary of the issue: knowing the cookie secret signs a client value, but it does not create the PostgreSQL session or the administrator record referenced by that value.

As a positive control, log in to the local test instance, retain the returned asid cookie, and request the same route. The request succeeds because the cookie points to a server-side PostgreSQL session containing a valid administrator userID.

Impact

The default keyboard cat secret allows anyone who knows the public source to produce valid signatures for EverShop session-cookie values.

The secret alone does not demonstrate arbitrary administrator impersonation in the current production flow because the session contents and administrator identity are stored and checked server-side. An authentication bypass requires an additional session-state weakness, such as disclosure or fixation of a valid Session ID, or attacker-controlled creation of the referenced session.

Remediation

  • Remove keyboard cat as the production default.
  • Require a deployment-specific high-entropy cookieSecret.
  • Fail closed at startup when the production secret is missing or equals a public placeholder.
  • Use separate secrets for administrator sessions, storefront sessions, and unrelated signed cookies.
  • Rotate the secret and invalidate existing sessions after deployment of the fix.

Source: evershopcommerce/evershop