Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions .cargo/audit.toml

This file was deleted.

102 changes: 4 additions & 98 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,6 @@ members = [
]
exclude = [
"fuzz",
# The governance workflow builds this external checker in a nested checkout.
".standards-js/tools/js-runtimes",
]
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ shape (Idris2 / Agda case studies in link:src/chapel/[`+src/chapel/+`]).
* *Three API surfaces*:
** GraphQL (async-graphql, port 8081)
** gRPC (tonic + Protocol Buffers, port 50051)
** REST (axum + OpenAPI/Swagger, port 8000)
** REST (axum + OpenAPI/ReDoc, port 8000)
* *AffineScript / AffineScript UI*: AffineScript components are being
ported to AffineScript-TEA as the stdlib primitives needed for the port
(Http / Promise / Json / Dict) land in
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ CancelProof

* *Framework:* Rust + axum + utoipa (OpenAPI)
* *Port:* 8000
* *Swagger UI:* http://localhost:8000/swagger-ui
* *API docs (ReDoc):* http://localhost:8000/redoc (raw spec: `+/api-docs/openapi.json+`)
* *Features:*
** RESTful JSON API
** OpenAPI 3.0 specification with Swagger UI
** OpenAPI 3.0 specification rendered with ReDoc
** Standard HTTP verbs (GET, POST, DELETE)
** Endpoints: `+/api/v1/provers+`, `+/api/v1/proofs+`,
`+/api/v1/proofs/:id/tactics+`
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/rest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ uuid = { version = "1.25.0", features = ["serde"] } # ids are minted by echidna

# OpenAPI
utoipa = { version = "6.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "10.0", features = ["axum"] }
utoipa-redoc = { version = "7.0", features = ["axum"] }

# ECHIDNA core
echidna = { path = "../../.." }
Expand Down
25 changes: 21 additions & 4 deletions src/interfaces/rest/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::time::Duration;
use tokio::sync::{Mutex, RwLock};
use tower_http::cors::CorsLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use utoipa_redoc::{Redoc, Servable};

mod ffi_wrapper;
mod handlers;
Expand Down Expand Up @@ -51,6 +51,17 @@ pub struct ProofSession {
pub start_time: std::time::Instant,
}

/// Runs the REST API with permissive CORS, ReDoc at `/redoc` and the OpenAPI
/// JSON specification at `/api-docs/openapi.json`.
///
/// Binds to `ECHIDNA_REST_ADDR`, defaulting to `127.0.0.1:8000` when the variable
/// is absent or is not valid Unicode. Probes Julia ML at `http://127.0.0.1:8090`
/// and initialises the FFI layer; failures of either check do not prevent startup.
///
/// # Panics
///
/// Panics if tracing initialisation, HTTP client creation, listener binding or
/// serving fails.
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
Expand Down Expand Up @@ -117,14 +128,20 @@ async fn main() {
// Consultant-mode Q&A — free-form question + optional context →
// LLM-shaped markdown answer via BoJ's cartridge router.
.route("/api/v1/consult", post(handlers::consult))
// OpenAPI documentation
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
// OpenAPI documentation: the raw spec, and ReDoc rendering it.
// ReDoc replaced Swagger UI because utoipa-swagger-ui builds
// against zip ^8.6, which has no fix for RUSTSEC-2026-0367..0369.
.route(
"/api-docs/openapi.json",
get(|| async { axum::Json(ApiDoc::openapi()) }),
)
.merge(Redoc::with_url("/redoc", ApiDoc::openapi()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serve the ReDoc renderer locally

Redoc::with_url uses utoipa-redoc's default HTML template, which loads redoc.standalone.js from the mutable https://cdn.redoc.ly/redoc/latest/... URL. Consequently, /redoc remains blank whenever the browser cannot access the public CDN—such as in an air-gapped or outbound-filtered deployment—even though the API and embedded specification are available locally; the previous Swagger UI served its build-time-bundled assets from this process. Please use a custom template backed by a locally served, pinned ReDoc bundle so the documented endpoint remains self-contained.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and real: utoipa-redoc 7.0's default template (res/redoc.html in the crate) is

<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>

so /redoc is a page we serve that executes whatever latest resolves to at request time, with no integrity attribute. The previous Swagger UI embedded its assets at build time, so this PR moved the dependency from build time to run time. Two consequences: the page is blank on an air-gapped or egress-filtered deployment, and the bundle is a mutable third-party script on a page served from our origin.

The fix I propose, in a follow-up on this stack rather than here (this PR's point is dropping the vendored zip):

  1. Redoc::with_url(...).custom_html(...) with the same template but the version pinned (/redoc/v2.x.y/bundles/...) and an integrity="sha384-…" attribute, computed from the bundle bytes. That keeps the tree free of the bundle and makes the dependency immutable.
  2. Optionally an ECHIDNA_REDOC_BUNDLE path so an operator can serve a local copy instead of the CDN; the ENV-VARS doc gets the entry.

I cannot compute the SRI hash from this session (the CDN is not reachable through its egress policy), so I am not pushing a half-pin. @hyperpolymath: say whether you want (1) alone or (1)+(2), and I will open it against this branch's merge.


Generated by Claude Code

.layer(CorsLayer::permissive())
.with_state(state);

let addr = std::env::var("ECHIDNA_REST_ADDR").unwrap_or_else(|_| "127.0.0.1:8000".to_string());
tracing::info!("REST API listening on http://{}", addr);
tracing::info!("Swagger UI available at http://{}/swagger-ui", addr);
tracing::info!("API docs (ReDoc) available at http://{}/redoc", addr);

let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
Expand Down
Loading