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
- Set `url_signature_secret` to a cryptographically random string of at least 32 bytes in your active config.json (or the corresponding env var).
- Generate a secret with `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` and paste it into config.
- Restart the backend process so the new config is loaded.
- 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
- Self-hosters: run a config validation script on startup that asserts all required keys are present.
- Include `url_signature_secret` in your deployment checklist and config templates.
- Add a health-check endpoint that verifies signing config at boot and fails loudly if missing.
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.