iflytek/astron-agent · error · HTTPException
Unauthorized
Error message
Unauthorized
What it means
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.
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
Example fix
# before
body = json.dumps(payload, indent=2) # signed a different serialization
send(body, signature=sign(token, json.dumps(payload)))
# after
canonical = json.dumps(payload, separators=(",", ":"), sort_keys=True)
sig = hmac.new(token.encode(), canonical.encode(), hashlib.sha256).hexdigest()
send(canonical, signature=sig) Defensive patterns
Strategy: validation
Validate before calling
sig = hmac.new(token.encode(), canonical_body.encode(), hashlib.sha256).hexdigest() assert hmac.compare_digest(sig, expected_header_sig.lower()) assert body_sent == canonical_body
Try / catch
try:
resp = requests.post(url, data=canonical, headers={"X-Signature": sig})
except requests.HTTPError as e:
if e.response.status_code == 401:
refresh_token_and_resign() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to build authentication URL
- Invalid workflow internal API credentials
- HMAC-SHA1 encryption error
- WebSocketClientAuthError
- invalid workflow gateway identity
AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12).
Data as JSON: /api/errors/416178eec5fb9f1f.
Report an issue: GitHub.
Appendix: source
Thrown at core/agent/api/v1/skill_sandbox_api.py:88
or not 1 <= len(timestamp_value) <= 20
or signature_value is None
or len(signature_value) != 64
or any(char not in "0123456789abcdefABCDEF" for char in signature_value)
):
raise ValueError
timestamp = int(timestamp_value)
now = int(time.time()) if now_seconds is None else now_seconds
if abs(now - timestamp) > EXECUTION_SIGNATURE_MAX_AGE_SECONDS:
raise ValueError
token = _load_runtime_credential_token()
canonical = timestamp_value.encode("ascii") + b"\n" + raw_body
expected = hmac.new(
token.encode("utf-8"), canonical, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature_value.lower()):
raise ValueError
except Exception:
raise HTTPException(status_code=401, detail="Unauthorized") from None
@skill_sandbox_router.post( # type: ignore[misc]
"/skill/sandbox-exec",
description="Execute a single skill command in the E2B sandbox (no artifact handling).",
response_model=SandboxExecResponse,
)
async def sandbox_exec(body: SandboxExecBody, request: Request) -> SandboxExecResponse:
_verify_execution_signature(
await request.body(),
request.headers.get(EXECUTION_TIMESTAMP_HEADER),
request.headers.get(EXECUTION_SIGNATURE_HEADER),
)
config = _build_config(body.sandbox)
configured = config.enabled
if not configured:
return SandboxExecResponse(
configured=False, message=SCRIPT_SANDBOX_UNCONFIGURED_MESSAGEView on GitHub (pinned to 5e758547a8)