{"record":{"id":"416178eec5fb9f1f","repo":"iflytek/astron-agent","slug":"unauthorized-skill-sandbox-api","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"core/agent/api/v1/skill_sandbox_api.py","lineNumber":88,"sourceCode":"            or not 1 <= len(timestamp_value) <= 20\n            or signature_value is None\n            or len(signature_value) != 64\n            or any(char not in \"0123456789abcdefABCDEF\" for char in signature_value)\n        ):\n            raise ValueError\n        timestamp = int(timestamp_value)\n        now = int(time.time()) if now_seconds is None else now_seconds\n        if abs(now - timestamp) > EXECUTION_SIGNATURE_MAX_AGE_SECONDS:\n            raise ValueError\n        token = _load_runtime_credential_token()\n        canonical = timestamp_value.encode(\"ascii\") + b\"\\n\" + raw_body\n        expected = hmac.new(\n            token.encode(\"utf-8\"), canonical, hashlib.sha256\n        ).hexdigest()\n        if not hmac.compare_digest(expected, signature_value.lower()):\n            raise ValueError\n    except Exception:\n        raise HTTPException(status_code=401, detail=\"Unauthorized\") from None\n\n\n@skill_sandbox_router.post(  # type: ignore[misc]\n    \"/skill/sandbox-exec\",\n    description=\"Execute a single skill command in the E2B sandbox (no artifact handling).\",\n    response_model=SandboxExecResponse,\n)\nasync def sandbox_exec(body: SandboxExecBody, request: Request) -> SandboxExecResponse:\n    _verify_execution_signature(\n        await request.body(),\n        request.headers.get(EXECUTION_TIMESTAMP_HEADER),\n        request.headers.get(EXECUTION_SIGNATURE_HEADER),\n    )\n    config = _build_config(body.sandbox)\n    configured = config.enabled\n    if not configured:\n        return SandboxExecResponse(\n            configured=False, message=SCRIPT_SANDBOX_UNCONFIGURED_MESSAGE","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/agent/api/v1/skill_sandbox_api.py#L70-L106","documentation":"HTTP 401 'Unauthorized' raised by _verify_execution_signature when the HMAC-SHA256 signature over the canonical request body does not match the signature computed with the shared sandbox token (or the signature/header is malformed). Protects the /skill/sandbox-exec endpoint from unauthenticated execution requests.","triggerScenarios":"POSTing to /skill/sandbox-exec with a missing, stale, or incorrectly computed HMAC signature header; signing a different body than the one sent (canonicalization mismatch); using the wrong token; clock/ordering drift if a timestamp is part of the canonical string.","commonSituations":"Clients recomputing the signature after JSON re-serialization changes key order or whitespace; test harnesses skipping the signing step; rotated or mismatched sandbox tokens between caller and service.","solutions":["Recompute the HMAC-SHA256 signature over the exact canonical body using the shared token and send it in the expected header","Ensure the signed canonical string is byte-identical to the transmitted body (serialize once, sign, send that exact payload)","Verify both sides use the same sandbox token/secret from configuration","If using a timestamp nonce, refresh the request rather than replaying an old signed body"],"exampleFix":"# before\nbody = json.dumps(payload, indent=2)  # signed a different serialization\nsend(body, signature=sign(token, json.dumps(payload)))\n\n# after\ncanonical = json.dumps(payload, separators=(\",\", \":\"), sort_keys=True)\nsig = hmac.new(token.encode(), canonical.encode(), hashlib.sha256).hexdigest()\nsend(canonical, signature=sig)","handlingStrategy":"validation","validationCode":"sig = hmac.new(token.encode(), canonical_body.encode(), hashlib.sha256).hexdigest()\nassert hmac.compare_digest(sig, expected_header_sig.lower())\nassert body_sent == canonical_body","typeGuard":null,"tryCatchPattern":"try:\n    resp = requests.post(url, data=canonical, headers={\"X-Signature\": sig})\nexcept requests.HTTPError as e:\n    if e.response.status_code == 401:\n        refresh_token_and_resign()","preventionTips":["Sign and send the exact same byte string (serialize once)","Keep the sandbox token in sync between caller and service","Refresh signatures per request; never replay stale signed bodies"],"tags":["hmac","authentication","http-401","signature"],"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"}