{"record":{"id":"780069907addf398","repo":"theonedev/onedev","slug":"this-api-can-only-be-accessed-via-cluster-credenti","errorCode":null,"errorMessage":"This api can only be accessed via cluster credential","messagePattern":"This api can only be accessed via cluster credential","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/cluster/ClusterResource.java","lineNumber":102,"sourceCode":"\tprivate PackBlobService packBlobService;\n\t\n\t@Inject\n\tprivate BuildService buildService;\n\t\n\t@Inject\n\tprivate RunCacheService cacheService;\n\t\n\t@Inject\n\tprivate WorkExecutionService workExecutionService;\n\t\n\t@Path(\"/site-files\")\n\t@Produces(MediaType.APPLICATION_OCTET_STREAM)\n\t@GET\n\tpublic Response downloadSiteFiles(@QueryParam(\"path\") String path,\n\t\t\t\t\t\t\t\t  @QueryParam(\"patterns\") String patterns,\n\t\t\t\t\t\t\t\t  @QueryParam(\"readLock\") String readLock) {\n\t\tif (!SecurityUtils.isSystem())\n\t\t\tthrow new UnauthorizedException(\"This api can only be accessed via cluster credential\");\n\n\t\tStreamingOutput output = os -> {\n\t\t\tRunnable sendFiles = () -> {\n\t\t\t\tFile directory = new File(Bootstrap.getSiteDir(), path);\n\t\t\t\tPatternSet patternSet = PatternSet.parse(patterns);\n\t\t\t\tpatternSet.getExcludes().add(SHARE_TEST_DIR + \"/**\");\n\t\t\t\tTarUtils.tar(directory, patternSet.getIncludes(), patternSet.getExcludes(), os, false);\n\t\t\t};\n\t\t\tif (readLock != null)\n\t\t\t\tread(readLock, sendFiles);\n\t\t\telse\n\t\t\t\tsendFiles.run();\n\t\t};\n\t\treturn ok(output).build();\n\t}\n\t\n\t@Path(\"/site-file\")\n\t@Produces(MediaType.APPLICATION_OCTET_STREAM)","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/cluster/ClusterResource.java#L84-L120","documentation":"ClusterResource.downloadSiteFiles is an internal cluster-only API that streams files from the server's site directory matching a pattern set. It rejects any caller not authenticated as the system principal via the cluster credential, throwing UnauthorizedException \"This api can only be accessed via cluster credential\".","triggerScenarios":"Calling GET on the site-files endpoint with normal user credentials, a regular access token, or no credentials instead of the server's cluster secret; pointing a third-party tool at this internal endpoint.","commonSituations":"Manually curling the endpoint for debugging without cluster auth; a member server whose cluster token is missing/mismatched fails the system check; firewall-routed requests stripping the credential header; API exploration with a regular PAT.","solutions":["Use the cluster credential (server secret) authentication for this endpoint — it is meant for inter-server cluster communication only.","Verify both member servers share the same cluster secret/token so SecurityUtils.isSystem() evaluates true.","Do not call this endpoint from user code; use the OneDev UI or public APIs to access site files.","Check reverse proxy / auth middleware is not stripping the cluster auth header before it reaches OneDev."],"exampleFix":"// before\ncurl -H \"Authorization: Bearer <personal-access-token>\" \\\n  https://onedev.example.com/api/cluster/site-files?path=cache\n\n// after (cluster member using cluster credential setup, not a PAT)\n// ensure the request originates from an authenticated cluster member;\n// configure identical cluster secrets on both servers, then call via the\n// cluster client rather than raw HTTP with user tokens.","handlingStrategy":"try-catch","validationCode":"if (!SecurityUtils.isSystem())\n    throw new IllegalStateException(\"Cluster endpoint requires cluster credential, not user auth\");","typeGuard":null,"tryCatchPattern":"try {\n    return target(\"cluster/site-files\").get(byte[].class);\n} catch (ForbiddenException | UnauthorizedException e) {\n    // not called with cluster credential; fix client authentication\n}","preventionTips":["Never call internal /api/cluster/* endpoints with personal access tokens.","Keep cluster secrets identical across all member servers.","Document these endpoints as internal-only for your team."],"tags":["security","authentication","cluster","unauthorized"],"backgroundTag":"permission-denied","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}