{"record":{"id":"02c90ca658f4d531","repo":"thingsboard/thingsboard","slug":"invalid-scope","errorCode":null,"errorMessage":"Invalid scope","messagePattern":"Invalid scope","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"application/src/main/java/org/thingsboard/server/controller/TbResourceController.java","lineNumber":469,"sourceCode":"        ByteArrayResource resource = new ByteArrayResource(data);\n        return ResponseEntity.ok()\n                .header(HttpHeaders.CONTENT_DISPOSITION, \"attachment;filename=\" + resourceInfo.getFileName())\n                .header(\"x-filename\", resourceInfo.getFileName())\n                .contentLength(resource.contentLength())\n                .header(\"Content-Type\", resourceInfo.getResourceType().getMediaType())\n                .cacheControl(CacheControl.noCache())\n                .eTag(resourceInfo.getEtag())\n                .body(resource);\n    }\n\n    private TbResourceInfo checkResourceInfo(String scope, ResourceType resourceType, String key, Operation operation) throws ThingsboardException {\n        TenantId tenantId;\n        if (scope.equals(\"tenant\")) {\n            tenantId = getTenantId();\n        } else if (scope.equals(\"system\")) {\n            tenantId = TenantId.SYS_TENANT_ID;\n        } else {\n            throw new IllegalArgumentException(\"Invalid scope\");\n        }\n\n        TbResourceInfo resourceInfo = resourceService.findResourceInfoByTenantIdAndKey(tenantId, resourceType, key);\n        checkEntity(getCurrentUser(), checkNotNull(resourceInfo), operation);\n        return resourceInfo;\n    }\n\n}\n","sourceCodeStart":451,"sourceCodeEnd":478,"githubUrl":"https://github.com/thingsboard/thingsboard/blob/45c30e83fa57356840d5f25d894002d94222d524/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java#L451-L478","documentation":"checkResourceInfo in TbResourceController resolves the owning tenant from a scope string: 'tenant' → current tenant, 'system' → SYS_TENANT_ID. Anything else throws IllegalArgumentException('Invalid scope') → HTTP 400. Used by resource download endpoints like /api/resource/{resourceType}/{scope}/{key}.","triggerScenarios":"GET /api/resource/JKS/tenant-resources/my-key, or scope values like 'Tenant', 'SYSTEM', 'global', 'all', or an empty path segment.","commonSituations":"URL templates built from enums or constants that don't match the exact lowercase literals; trailing whitespace or case differences from user-entered scope; clients ported from another resource API with different scope names.","solutions":["Use exactly 'tenant' or 'system' (lowercase) in the scope path segment","Normalize/trim the scope value and validate it against the two literals before building the URL","Check the OpenAPI spec for the endpoint to confirm allowed values"],"exampleFix":"// before\nconst url = `/api/resource/${type}/${scope}/${key}`; // scope = 'Tenant'\n\n// after\nconst s = String(scope).trim().toLowerCase();\nif (!['tenant', 'system'].includes(s)) throw new Error('scope must be tenant|system');\nconst url = `/api/resource/${type}/${s}/${key}`;","handlingStrategy":"validation","validationCode":"const s = scope?.trim().toLowerCase();\nif (s !== 'tenant' && s !== 'system') throw new Error(\"scope must be 'tenant' or 'system'\");","typeGuard":"function isResourceScope(v) { return v === 'tenant' || v === 'system'; }","tryCatchPattern":"catch (e) { if (e.status === 400 && /Invalid scope/.test(e.message)) { normalizeScopeAndRetry(); } else throw e; }","preventionTips":["Lowercase and trim scope before interpolating into resource URLs","Keep a constant list of the two allowed scope literals"],"tags":["resources","rest-api","validation","thingsboard"],"backgroundTag":null,"analyzedSha":"45c30e83fa57356840d5f25d894002d94222d524","analyzedAt":"2026-08-14T11:45:36.599Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}