{"record":{"id":"87a6b08152a19d18","repo":"SillyTavern/SillyTavern","slug":"forbidden-no-permission-to-list-branches-of-globa","errorCode":null,"errorMessage":"Forbidden: No permission to list branches of global extensions.","messagePattern":"Forbidden: No permission to list branches of global extensions\\.","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"src/endpoints/extensions.js","lineNumber":231,"sourceCode":"        return response.status(500).send('Internal Server Error. Check the server logs for more details.');\n    }\n});\n\nrouter.post('/branches', async (request, response) => {\n    try {\n        if (typeof request.body.extensionName !== 'string') {\n            return response.status(400).send('Bad Request: A valid extensionName is required in the request body.');\n        }\n\n        const { extensionName, global } = request.body;\n        const extensionNameSanitized = sanitize(extensionName);\n        if (!extensionNameSanitized) {\n            return response.status(400).send('Bad Request: A valid extensionName is required in the request body.');\n        }\n\n        if (global && !request.user.profile.admin) {\n            console.error(`User ${request.user.profile.handle} does not have permission to list branches of global extensions.`);\n            return response.status(403).send('Forbidden: No permission to list branches of global extensions.');\n        }\n\n        const basePath = global ? PUBLIC_DIRECTORIES.globalExtensions : request.user.directories.extensions;\n        const extensionPath = path.join(basePath, extensionNameSanitized);\n\n        if (!fs.existsSync(extensionPath)) {\n            return response.status(404).send(`Directory does not exist at ${extensionPath}`);\n        }\n\n        const git = simpleGit({ baseDir: extensionPath, ...OPTIONS });\n        // Unshallow the repository if it is shallow\n        const isShallow = await git.revparse(['--is-shallow-repository']) === 'true';\n        if (isShallow) {\n            console.info(`Unshallowing the repository at ${extensionPath}`);\n            await git.fetch('origin', ['--unshallow']);\n        }\n\n        // Fetch all branches","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/SillyTavern/SillyTavern/blob/8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8/src/endpoints/extensions.js#L213-L249","documentation":"The /branches endpoint (extensions.js:229-231) checks admin privileges when the request body has global set to truthy. Non-admin users cannot list branches of extensions in the global directory. This is the same authorization pattern used by /install and /update.","triggerScenarios":"POST /api/extensions/branches with { extensionName: \"...\", global: true } (or any truthy value) when request.user.profile.admin is falsy.","commonSituations":"Non-admin user has global: true set inadvertently; frontend bug sends the flag; or the user misunderstands their privilege level.","solutions":["Remove the global field (or set it to false) for user-scoped branch listing.","If global branch listing is needed, have an admin perform the request."],"exampleFix":"// before — non-admin trying global branches\nfetch('/api/extensions/branches', {\n  method: 'POST',\n  body: JSON.stringify({ extensionName: 'my-ext', global: true }),\n});\n\n// after — user-scoped\nfetch('/api/extensions/branches', {\n  method: 'POST',\n  body: JSON.stringify({ extensionName: 'my-ext' }),\n});","handlingStrategy":"validation","validationCode":"// Only set global when the user is an admin.\nfunction buildBranchesPayload(extensionName, isAdmin, globalRequested) {\n  const payload = { extensionName };\n  if (globalRequested && isAdmin) {\n    payload.global = true;\n  } else if (globalRequested && !isAdmin) {\n    console.warn('User is not admin — falling back to user-scoped branch listing.');\n  }\n  return payload;\n}\n\nconst payload = buildBranchesPayload(name, user.profile.admin, wantGlobal);\nawait fetch('/api/extensions/branches', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(payload),\n});","typeGuard":"/** Checks whether the current user may list branches of global extensions. */\nfunction canListGlobalBranches(user) {\n  return Boolean(user?.profile?.admin === true);\n}","tryCatchPattern":null,"preventionTips":["Default to user-scoped branch listing by omitting the global field.","Only send global: true when the user is confirmed as admin.","Handle 403 by falling back to user-scoped or prompting admin login."],"tags":["extensions","authorization","admin","http-403"],"backgroundTag":null,"analyzedSha":"8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8","analyzedAt":"2026-08-13T07:48:40.832Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}