{"record":{"id":"59b5d35aeb3a1a5d","repo":"SillyTavern/SillyTavern","slug":"internal-server-error-59b5d3","errorCode":null,"errorMessage":"Internal Server Error","messagePattern":"Internal Server Error","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"src/endpoints/secrets.js","lineNumber":525,"sourceCode":"}\n\nexport const router = express.Router();\n\nrouter.post('/write', (request, response) => {\n    try {\n        const { key, value, label } = request.body;\n\n        if (!key || typeof value !== 'string') {\n            return response.status(400).send('Invalid key or value');\n        }\n\n        const manager = new SecretManager(request.user.directories);\n        const id = manager.writeSecret(key, value, label);\n\n        return response.send({ id });\n    } catch (error) {\n        console.error('Error writing secret:', error);\n        return response.sendStatus(500);\n    }\n});\n\nrouter.post('/read', (request, response) => {\n    try {\n        const manager = new SecretManager(request.user.directories);\n        const state = manager.getSecretState();\n        return response.send(state);\n    } catch (error) {\n        console.error('Error reading secret state:', error);\n        return response.send({});\n    }\n});\n\nrouter.post('/view', (request, response) => {\n    try {\n        if (!allowKeysExposure) {\n            console.error('secrets.json could not be viewed unless allowKeysExposure in config.yaml is set to true');","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/SillyTavern/SillyTavern/blob/8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8/src/endpoints/secrets.js#L507-L543","documentation":"Generic 500 returned by the catch block of POST /api/secrets/write when SecretManager.writeSecret throws. writeSecret reads secrets.json, JSON.parses it, deactivates existing entries, pushes the new secret, then atomically rewrites the file via write-file-atomic. Any failure in that chain (corrupted JSON, unreadable/unwritable file, disk full during the atomic temp-file swap) bubbles up here.","triggerScenarios":"POST /api/secrets/write with a valid key/value but where secrets.json is malformed JSON, where the user's root directory is not writable, where the disk is full, or where an external process (antivirus, indexer) holds an exclusive lock on secrets.json during the atomic write.","commonSituations":"User hand-edited data/<user>/secrets.json and introduced a syntax error; a partial write from a crashed server left truncated JSON; data directory moved onto a read-only mount; container mounted data volume read-only; Windows Defender locking the file during write.","solutions":["Read the server console output: the line 'Error writing secret:' prints the underlying Error (ENOENT, EACCES, SyntaxError, ENOSPC).","If SyntaxError: open data/<user>/secrets.json and validate/repair the JSON, or restore from data/<user>/backups/secrets_migration_*.json.","If EACCES/EROFS: fix ownership/permissions on the user root directory (chmod/chown) or remount the data volume read-write.","If unrecoverable: back up then delete secrets.json; SecretManager._ensureSecretsFile recreates an empty one on next access."],"exampleFix":"// before: corrupted secrets.json breaks every write\n// after: validate and self-heal on startup\nimport { SecretManager } from './src/endpoints/secrets.js';\ntry {\n  new SecretManager(user.directories)._readSecretsFile();\n} catch (e) {\n  fs.copyFileSync(secretsPath, `${secretsPath}.broken-${Date.now()}`);\n  fs.rmSync(secretsPath, { force: true }); // recreated empty on next write\n}","handlingStrategy":"try-catch","validationCode":"// Before calling /api/secrets/write, sanity-check the target file is parseable\nconst fs = require('node:fs');\nfunction canWriteSecrets(rootDir) {\n  const p = require('node:path').join(rootDir, 'secrets.json');\n  if (!fs.existsSync(p)) return true; // will be auto-created\n  try { JSON.parse(fs.readFileSync(p, 'utf8')); return true; }\n  catch { return false; }\n}","typeGuard":"/** @param {any} b */\nfunction isValidWriteBody(b) {\n  return !!b && typeof b === 'object'\n    && typeof b.key === 'string' && b.key.length > 0\n    && typeof b.value === 'string';\n}","tryCatchPattern":"try {\n  const r = await fetch('/api/secrets/write', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ key, value, label }) });\n  if (r.status === 500) throw new Error('secret store unavailable — check secrets.json');\n  return await r.json();\n} catch (e) { showUser('Could not save secret: ' + e.message); }","preventionTips":["Never hand-edit secrets.json; always use the /write endpoint so the atomic write path runs.","Run SillyTavern with a dedicated service account that owns data/<user>/.","Back up data/<user>/secrets.json before upgrades.","Mount the data directory read-write; never read-only."],"tags":["secrets","filesystem","json","write-file-atomic","iis"],"backgroundTag":null,"analyzedSha":"8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8","analyzedAt":"2026-08-13T07:48:40.832Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}