{"record":{"id":"ef9b1a66f0a97bf2","repo":"iflytek/astron-agent","slug":"unauthorized-ef9b1a","errorCode":"UNAUTHORIZED","errorMessage":"BusinessException(ResponseEnum.UNAUTHORIZED)","messagePattern":"BusinessException\\(ResponseEnum\\.UNAUTHORIZED\\)","errorType":"error_code","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/service/skill/SkillSandboxConfigService.java","lineNumber":133,"sourceCode":"        SkillSandboxConfig config = getActiveConfig(uid, spaceId);\n        SkillSandboxRuntimeRefDto dto = new SkillSandboxRuntimeRefDto();\n        dto.setProvider(PROVIDER_E2B);\n        dto.setEnabled(config != null);\n        dto.setUid(uid);\n        dto.setSpaceId(spaceId);\n        return dto;\n    }\n\n    /**\n     * Resolve the E2B credential only for the authenticated private broker. A workflow reference\n     * derives scope from the database; standalone agent calls must provide a currently authorized\n     * uid/space pair.\n     */\n    public SkillSandboxRuntimeCredentialDto getRuntimeCredential(\n            String serviceToken, String flowId, String uid, Long spaceId) {\n        if (runtimeCredentialTokenProvider == null\n                || !runtimeCredentialTokenProvider.matches(serviceToken)) {\n            throw new BusinessException(ResponseEnum.UNAUTHORIZED);\n        }\n        assertExplicitScope(uid, spaceId);\n        SkillSandboxConfig config;\n        if (StringUtils.isNotBlank(flowId)) {\n            List<Workflow> workflows = workflowMapper.selectList(\n                    Wrappers.lambdaQuery(Workflow.class)\n                            .eq(Workflow::getFlowId, StringUtils.trim(flowId))\n                            .eq(Workflow::getDeleted, Boolean.FALSE)\n                            .last(\"limit 2\"));\n            if (workflows == null || workflows.size() != 1) {\n                throw new BusinessException(ResponseEnum.WORKFLOW_NOT_EXIST);\n            }\n            Workflow workflow = workflows.getFirst();\n            assertWorkflowExecutionScope(workflow, uid, spaceId);\n            config = getActiveConfigForTrustedScope(workflow.getUid(), workflow.getSpaceId());\n        } else {\n            config = getActiveConfigForTrustedScope(uid, spaceId);\n        }","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/service/skill/SkillSandboxConfigService.java#L115-L151","documentation":"SkillSandboxConfigService.getRuntimeCredential validates the internal service token before issuing sandbox runtime credentials (provider, API key, timeout, internet flag). If no runtimeCredentialTokenProvider is configured or the presented serviceToken does not match, it throws UNAUTHORIZED. This is the service-to-service authentication gate for the sandbox runtime.","triggerScenarios":"Calling getRuntimeCredential (or its REST endpoint) with a null/missing/incorrect serviceToken, or with the runtime credential feature disabled so runtimeCredentialTokenProvider is null.","commonSituations":"Sandbox runtime calling with a stale or rotated token after redeployment; token env var not set on one side (provider never configured); caller omitting the Authorization/service-token header entirely.","solutions":["Ensure the sandbox runtime sends the exact configured service token header.","Confirm the same token value is configured in both the toolkit service and the sandbox runtime (env var / config).","Restart both services after rotating the token so in-memory providers pick up the new value.","If the feature was just added, verify the runtimeCredentialTokenProvider bean is actually created and injected."],"exampleFix":"// before\nhttp.post(\"/skill-sandbox/credential\", body); // no token header\n// after\nhttp.post(\"/skill-sandbox/credential\", body,\n    Map.of(\"X-Service-Token\", configuredServiceToken));","handlingStrategy":"validation","validationCode":"if (serviceToken == null || !serviceToken.equals(expectedToken)) {\n    throw new IllegalArgumentException(\"service token missing or mismatched\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    cred = runtimeClient.getCredential(token, flowId, uid, spaceId);\n} catch (BusinessException e) {\n    if (\"UNAUTHORIZED\".equals(e.getCode())) rotateAndRetryWithFreshToken();\n}","preventionTips":["Inject the service token via env var identically on both sides of the call.","Rotate tokens with a coordinated redeploy, never one service at a time.","Send the token header on every internal request (use a shared HTTP client interceptor).","Assert at startup that the token is configured and non-empty."],"tags":["java","auth","service-token","security"],"backgroundTag":"authentication-required","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}