HeyPuter/puter · critical · HttpError

internal_error

internal_error

Error message

Server misconfiguration: url_signature_secret not set

What it means

The server requires a `url_signature_secret` config value to HMAC-sign file download URLs. `signingConfigFromAppConfig()` throws HTTP 500 when the key is absent, not a string, or empty — it is a deployment defect, not a client error. Every signed-URL route (file read, stat, thumbnail) is unusable until the secret is set.

Source

Thrown at src/backend/controllers/fs/legacyFsHelpers.ts:507

        response.thumbnail as string | null,
    );

    return response;
}

export { normalizeAbsolutePath };

// -- Signing ---------------------------------------------------------

/**
 * Pull the signing config off the app config. Throws if either value is missing
 * — these are required for signed URL routes to function.
 */
export function signingConfigFromAppConfig(config: IConfig): SigningConfig {
    const secret = config.url_signature_secret;
    const apiBaseUrl = config.api_base_url;
    if (typeof secret !== 'string' || secret.length === 0) {
        throw new HttpError(
            500,
            'Server misconfiguration: url_signature_secret not set',
            { legacyCode: 'internal_error' },
        );
    }
    if (typeof apiBaseUrl !== 'string' || apiBaseUrl.length === 0) {
        throw new HttpError(
            500,
            'Server misconfiguration: api_base_url not set',
            { legacyCode: 'internal_error' },
        );
    }
    return { secret, apiBaseUrl };
}

/** Convenience wrapper: turn an FSEntry into a signed-file response object. */
export function signEntry(
    entry: {

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Set `url_signature_secret` to a cryptographically random string of at least 32 bytes in your active config.json (or the corresponding env var).
  2. Generate a secret with `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` and paste it into config.
  3. Restart the backend process so the new config is loaded.
  4. Verify by issuing a file-stat request and confirming the returned download URL includes a `signature` query parameter.

Example fix

// before (config.json)
{
  "api_base_url": "https://api.example.com"
  // url_signature_secret missing
}

// after
{
  "api_base_url": "https://api.example.com",
  "url_signature_secret": "<64-char hex random string>"
}
Defensive patterns

Strategy: try-catch

Try / catch

// Client cannot prevent this — it is a server config error.
// Catch and surface to the user / ops team.
try {
  const res = await fetch('/api/drivers/call', { /* ... */ });
  if (!res.ok) throw await res.json();
} catch (e) {
  if (e.message?.includes('url_signature_secret')) {
    console.error('Server is misconfigured. Contact the administrator.');
  }
}

Prevention

When it happens

Trigger: Any request path that produces a signed file URL calls `signingConfigFromAppConfig(config)`. If `config.url_signature_secret` is unset/empty/non-string, the call throws before a URL can be built. Triggered by file-stat, file-read, and thumbnail responses that include signed download links.

Common situations: Self-hosting Puter from a config template with the signing secret left blank; rotating secrets and forgetting to repopulate the field; loading the wrong config file (e.g., dev config in production) that omits the key.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/e7525886d7997b3d. Report an issue: GitHub.