linera-io/linera-protocol · error · ExecutionError

UnauthorizedHttpRequest

UnauthorizedHttpRequest

Error message

ExecutionError::UnauthorizedHttpRequest(url)

What it means

The HTTP oracle (PerformHttpRequest) only permits requests whose host appears in the current committee's http_request_allow_list. UnauthorizedHttpRequest is raised in three cases: the URL parses without a host, the chain has no current committee, or the host is not in the allow-list (execution_state_actor.rs:531-541). This keeps deterministic, replayable oracle calls bounded to approved endpoints.

Source

Thrown at linera-execution/src/execution_state_actor.rs:539

                            .headers
                            .into_iter()
                            .map(|http::Header { name, value }| {
                                Ok((name.parse()?, value.try_into()?))
                            })
                            .collect::<Result<HeaderMap, ExecutionError>>()?;

                        let url = Url::parse(&request.url)?;
                        let host = url
                            .host_str()
                            .ok_or_else(|| ExecutionError::UnauthorizedHttpRequest(url.clone()))?;

                        let (_epoch, committee) = system
                            .current_committee()
                            .await?
                            .ok_or_else(|| ExecutionError::UnauthorizedHttpRequest(url.clone()))?;
                        let allowed_hosts = &committee.policy().http_request_allow_list;

                        ensure!(
                            allowed_hosts.contains(host),
                            ExecutionError::UnauthorizedHttpRequest(url)
                        );

                        let request = Client::new()
                            .request(request.method.into(), url)
                            .body(request.body)
                            .headers(headers);
                        #[cfg(not(web))]
                        let request = request.timeout(linera_base::time::Duration::from_millis(
                            committee.policy().http_request_timeout_ms,
                        ));

                        let response = request.send().await?;

                        let mut response_size_limit =
                            committee.policy().maximum_http_response_bytes;

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Add the request host to the committee policy's http_request_allow_list and activate the epoch or committee that includes it
  2. Route requests through an already allow-listed host or gateway that proxies the target API
  3. Validate the URL has a host before issuing the request from the contract
  4. Confirm system.current_committee() resolves (the chain is in an active epoch) when the request runs

Example fix

// before: committee policy
http_request_allow_list: ["api.gateway.example"].into(),
// contract requests https://prices.example/feed -> UnauthorizedHttpRequest

// after
http_request_allow_list: ["api.gateway.example", "prices.example"].into(),
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: check the host against a locally mirrored allow-list before the contract call
fn host_allowed(url: &str, allow_list: &BTreeSet<String>) -> Result<bool, url::ParseError> {
    let parsed = url::Url::parse(url)?;
    Ok(parsed.host_str().map(|h| allow_list.contains(h)).unwrap_or(false))
}

if !host_allowed(&req_url, &committee_policy.http_request_allow_list)? {
    return Err(anyhow!("host not in http_request_allow_list"));
}
submit_http_oracle_request(req_url)?;

Type guard

fn is_unauthorized_http_request(err: &ExecutionError) -> bool {
    matches!(err, ExecutionError::UnauthorizedHttpRequest(_))
}

Try / catch

match client.perform_http_request(req).await {
    Ok(resp) => resp,
    Err(ref e) if is_unauthorized_http_request(e) => {
        // deterministic policy failure: surface which host needs allow-listing
        return Err(anyhow!("request host is not allow-listed by committee policy"));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: A contract performs an HTTP request to a URL whose host is absent from the committee policy's http_request_allow_list, or the request executes before the chain has an active committee or epoch. Also triggered by malformed URLs that yield no host component.

Common situations: Pointing the oracle at an API host never added to committee policy; local or dev networks regenerated without the host; URL scheme quirks (e.g. data: or scheme-relative URLs with no host); requesting during epoch transitions when current_committee() is None.

Understand the failure class

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/2f166ad457c33958. Report an issue: GitHub.