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

  1. Recompute the HMAC-SHA256 signature over the exact canonical body using the shared token and send it in the expected header
  2. Ensure the signed canonical string is byte-identical to the transmitted body (serialize once, sign, send that exact payload)
  3. Verify both sides use the same sandbox token/secret from configuration
  4. 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

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

Related errors


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_MESSAGE

View on GitHub (pinned to 5e758547a8)