use std::{path::Path, sync::Arc};
use axum::{
body::Body,
http::{HeaderMap, Request, StatusCode},
middleware::Next,
response::Response,
};
use exo_node::crosschecked_anchor_http::CrossCheckedBearerVerifier;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
pub const LIVESAFE_PUBLIC_ADAPTER_OUTPUT_AUTHORIZATION_ROUTE: &str =
"/api/v1/avc/livesafe/public-adapter-output-authorization";
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub(crate) struct BearerTokenVerifier {
digest: [u8; 32],
}
impl BearerTokenVerifier {
pub(crate) fn from_bearer(bearer: &str) -> Self {
Self {
digest: Self::digest(bearer),
}
}
fn digest(bearer: &str) -> [u8; 32] {
*blake3::hash(bearer.as_bytes()).as_bytes()
}
pub(crate) fn verifies(&self, bearer: &str) -> bool {
let mut candidate = Self::digest(bearer);
let matches = bool::from(candidate.ct_eq(&self.digest));
candidate.zeroize();
matches
}
}
#[derive(Clone)]
pub struct BearerAuth {
verifier: Arc<BearerTokenVerifier>,
}
impl BearerAuth {
pub fn from_bearer(bearer: &str) -> Self {
Self {
verifier: Arc::new(BearerTokenVerifier::from_bearer(bearer)),
}
}
pub fn verify_headers(&self, headers: &HeaderMap) -> Result<(), StatusCode> {
verify_bearer_header(headers, self)
}
}
#[derive(Clone, Default)]
pub struct ScopedBearerAuth {
livesafe_public_adapter_output_authorization: Option<Arc<BearerTokenVerifier>>,
crosschecked_anchor: Option<CrossCheckedBearerVerifier>,
}
impl ScopedBearerAuth {
pub fn none() -> Self {
Self::default()
}
pub fn livesafe_public_adapter_output_authorization(token: Zeroizing<String>) -> Self {
let verifier = BearerTokenVerifier::from_bearer(token.as_str());
Self {
livesafe_public_adapter_output_authorization: Some(Arc::new(verifier)),
crosschecked_anchor: None,
}
}
pub fn with_crosschecked_anchor(mut self, verifier: CrossCheckedBearerVerifier) -> Self {
self.crosschecked_anchor = Some(verifier);
self
}
pub fn livesafe_public_adapter_output_authorization_configured(&self) -> bool {
self.livesafe_public_adapter_output_authorization
.as_ref()
.is_some()
}
pub fn crosschecked_anchor_configured(&self) -> bool {
self.crosschecked_anchor.is_some()
}
}
pub fn generate_admin_token() -> Result<Zeroizing<String>, getrandom::Error> {
generate_admin_token_with_entropy(|bytes| getrandom::getrandom(bytes))
}
fn generate_admin_token_with_entropy<E, F>(fill_entropy: F) -> Result<Zeroizing<String>, E>
where
F: FnOnce(&mut [u8; 32]) -> Result<(), E>,
{
let mut bytes = [0u8; 32];
fill_entropy(&mut bytes)?;
let token = Zeroizing::new(hex::encode(bytes));
bytes.zeroize();
Ok(token)
}
pub fn write_admin_token_file(path: &Path, token: &str) -> anyhow::Result<()> {
crate::private_file::write_private_replace(path, token.as_bytes())
}
fn bearer_header_value(headers: &HeaderMap) -> Result<&str, StatusCode> {
let mut headers = headers.get_all(axum::http::header::AUTHORIZATION).iter();
let header = headers.next().and_then(|value| value.to_str().ok());
if headers.next().is_some() {
return Err(StatusCode::UNAUTHORIZED);
}
match header {
Some(value) if value.starts_with("Bearer ") => Ok(&value["Bearer ".len()..]),
_ => Err(StatusCode::UNAUTHORIZED),
}
}
fn verify_bearer_header(headers: &HeaderMap, auth: &BearerAuth) -> Result<(), StatusCode> {
let provided = bearer_header_value(headers)?;
if auth.verifier.verifies(provided) {
Ok(())
} else {
Err(StatusCode::FORBIDDEN)
}
}
fn verify_admin_or_livesafe_public_output_bearer(
headers: &HeaderMap,
auth: &BearerAuth,
scoped_auth: &ScopedBearerAuth,
) -> Result<(), StatusCode> {
let provided = bearer_header_value(headers)?;
if auth.verifier.verifies(provided) {
return Ok(());
}
if let Some(verifier) = &scoped_auth.livesafe_public_adapter_output_authorization {
if verifier.verifies(provided) {
return Ok(());
}
}
Err(StatusCode::FORBIDDEN)
}
fn verify_crosschecked_anchor_bearer(
headers: &HeaderMap,
scoped_auth: &ScopedBearerAuth,
) -> Result<(), StatusCode> {
let provided = bearer_header_value(headers)?;
let Some(verifier) = &scoped_auth.crosschecked_anchor else {
return Err(StatusCode::FORBIDDEN);
};
if verifier.verifies(provided) {
Ok(())
} else {
Err(StatusCode::FORBIDDEN)
}
}
pub async fn require_bearer(
auth: BearerAuth,
request: Request<Body>,
next: Next,
) -> Result<Response, StatusCode> {
verify_bearer_header(request.headers(), &auth)?;
Ok(next.run(request).await)
}
fn is_sensitive_read_path(path: &str) -> bool {
path == "/api/v1/receipts"
|| path.starts_with("/api/v1/receipts/")
|| path.starts_with("/api/v1/provenance/")
|| path.starts_with("/api/v1/avc/")
|| path.starts_with("/api/v1/authority/")
|| path == "/api/v1/challenges"
|| path.starts_with("/api/v1/challenges/")
|| path == "/exoforge"
|| path.starts_with("/api/v1/forge/")
|| (path.starts_with("/api/v1/economy/") && path != "/api/v1/economy/policy/active")
|| (path.starts_with("/api/v1/agents/") && path.ends_with("/avcs"))
}
fn is_zerodentity_local_signed_write(method: &axum::http::Method, path: &str) -> bool {
const PREFIX: &str = "/api/v1/0dentity/";
let Some(rest) = path.strip_prefix(PREFIX) else {
return false;
};
if rest.is_empty() {
return false;
}
let mut segments = rest.split('/');
let Some(did_segment) = segments.next() else {
return false;
};
if did_segment.is_empty() {
return false;
}
if method == axum::http::Method::POST {
return matches!((segments.next(), segments.next()), (Some("attest"), None));
}
method == axum::http::Method::DELETE && segments.next().is_none()
}
fn is_avc_receipts_emit_route(method: &axum::http::Method, path: &str) -> bool {
method == axum::http::Method::POST
&& matches!(
path,
"/api/v1/avc/receipts/emit" | "/api/v1/avc/llm-usage/receipts/emit"
)
}
fn is_livesafe_public_adapter_output_authorization_route(
method: &axum::http::Method,
path: &str,
) -> bool {
method == axum::http::Method::POST && path == LIVESAFE_PUBLIC_ADAPTER_OUTPUT_AUTHORIZATION_ROUTE
}
fn is_crosschecked_anchor_route(method: &axum::http::Method, path: &str) -> bool {
const ROUTE: &str = "/api/v1/anchors/crosschecked";
if method == axum::http::Method::POST {
return path == ROUTE;
}
if method != axum::http::Method::GET {
return false;
}
path.strip_prefix("/api/v1/anchors/crosschecked/")
.is_some_and(|action_hash| !action_hash.is_empty() && !action_hash.contains('/'))
}
const AVC_EMIT_RECEIPT_CARVE_OUT_MAX_BODY_BYTES: usize = 64 * 1024;
async fn avc_emit_receipt_carve_out(request: Request<Body>) -> (Request<Body>, bool) {
let (parts, body) = request.into_parts();
let path = parts.uri.path().to_owned();
let bytes = match axum::body::to_bytes(body, AVC_EMIT_RECEIPT_CARVE_OUT_MAX_BODY_BYTES).await {
Ok(bytes) => bytes,
Err(_) => {
return (Request::from_parts(parts, Body::empty()), false);
}
};
let has_subject_signature = match path.as_str() {
"/api/v1/avc/receipts/emit" => {
serde_json::from_slice::<crate::avc::EmitReceiptRequest>(&bytes)
.is_ok_and(|parsed| !parsed.subject_signature.is_empty())
}
"/api/v1/avc/llm-usage/receipts/emit" => {
serde_json::from_slice::<crate::avc::LlmUsageReceiptEmitRequest>(&bytes)
.is_ok_and(|parsed| !parsed.subject_signature.is_empty())
}
_ => false,
};
let rebuilt = Request::from_parts(parts, Body::from(bytes));
(rebuilt, has_subject_signature)
}
pub async fn require_bearer_on_writes(
auth: BearerAuth,
request: Request<Body>,
next: Next,
) -> Result<Response, StatusCode> {
require_bearer_on_writes_with_scoped_bearers(auth, ScopedBearerAuth::none(), request, next)
.await
}
pub async fn require_bearer_on_writes_with_scoped_bearers(
auth: BearerAuth,
scoped_auth: ScopedBearerAuth,
request: Request<Body>,
next: Next,
) -> Result<Response, StatusCode> {
let method = request.method().clone();
let path = request.uri().path().to_owned();
if is_crosschecked_anchor_route(&method, &path) {
verify_crosschecked_anchor_bearer(request.headers(), &scoped_auth)?;
return Ok(next.run(request).await);
}
let is_public_read = (method == axum::http::Method::GET || method == axum::http::Method::HEAD)
&& !is_sensitive_read_path(&path);
if is_public_read || is_zerodentity_local_signed_write(&method, &path) {
return Ok(next.run(request).await);
}
if is_livesafe_public_adapter_output_authorization_route(&method, &path) {
verify_admin_or_livesafe_public_output_bearer(request.headers(), &auth, &scoped_auth)?;
return Ok(next.run(request).await);
}
if is_avc_receipts_emit_route(&method, &path) {
let (request, has_subject_signature) = avc_emit_receipt_carve_out(request).await;
if has_subject_signature {
return Ok(next.run(request).await);
}
verify_bearer_header(request.headers(), &auth)?;
return Ok(next.run(request).await);
}
verify_bearer_header(request.headers(), &auth)?;
Ok(next.run(request).await)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use axum::{
Router,
body::Body,
http::{Request, StatusCode},
middleware,
routing::{delete, get, post},
};
use tower::ServiceExt;
use super::*;
fn test_auth() -> BearerAuth {
BearerAuth::from_bearer("test-token-abc123")
}
fn startup_drops_admin_token_before_first_await_after_configuration(startup: &str) -> bool {
let Some(configuration_start) = startup.find("let crosschecked_anchor_config =") else {
return false;
};
let Some(configuration_end_offset) = startup[configuration_start..].find(';') else {
return false;
};
let configuration_completion = configuration_start + configuration_end_offset + 1;
let post_configuration = &startup[configuration_completion..];
let Some(drop_offset) = post_configuration.find("drop(admin_token);") else {
return false;
};
let Some(first_await_offset) = post_configuration.find(".await") else {
return false;
};
drop_offset < first_await_offset
}
#[test]
fn bearer_tokens_are_compared_as_fixed_size_digests() {
let verifier = BearerTokenVerifier::from_bearer("expected-node-token");
let long = "x".repeat(4_096);
let cases = [
("", false),
("short", false),
("expected-node-token", true),
(long.as_str(), false),
];
for (provided, expected) in cases {
assert_eq!(BearerTokenVerifier::digest(provided).len(), 32);
assert_eq!(verifier.verifies(provided), expected);
}
}
fn test_app() -> Router {
let auth = test_auth();
Router::new()
.route("/read", get(|| async { "ok" }))
.route("/api/v1/receipts/:hash", get(|| async { "receipt" }))
.route("/api/v1/receipts", get(|| async { "receipts" }))
.route("/api/v1/provenance/:hash", get(|| async { "provenance" }))
.route("/api/v1/avc/:id", get(|| async { "credential" }))
.route("/api/v1/agents/:did/avcs", get(|| async { "credentials" }))
.route("/api/v1/challenges", get(|| async { "challenges" }))
.route("/api/v1/challenges/:id", get(|| async { "challenge" }))
.route("/exoforge", get(|| async { "forge dashboard" }))
.route("/api/v1/forge/tasks", get(|| async { "forge tasks" }))
.route("/api/v1/forge/stats", get(|| async { "forge stats" }))
.route("/api/v1/forge/activity", get(|| async { "forge activity" }))
.route(
"/api/v1/economy/bailment-terms/:id",
get(|| async { "bailment terms" }),
)
.route(
"/api/v1/economy/policy/active",
get(|| async { "active policy" }),
)
.route(
"/api/v1/0dentity/:did/attest",
post(|| async { "signed-attest" }),
)
.route(
"/api/v1/0dentity/:did",
delete(|| async { "signed-delete" }),
)
.route(
"/api/v1/0dentity/:did/score",
post(|| async { "unexpected-write" }),
)
.route("/write", post(|| async { "ok" }))
.layer(middleware::from_fn(move |req, next| {
let a = auth.clone();
require_bearer_on_writes(a, req, next)
}))
}
fn livesafe_public_output_scoped_bearer_test_app(scoped_bearer: Option<&str>) -> Router {
let auth = test_auth();
let scoped_auth = scoped_bearer
.map(|token| {
ScopedBearerAuth::livesafe_public_adapter_output_authorization(Zeroizing::new(
token.to_owned(),
))
})
.unwrap_or_else(ScopedBearerAuth::none);
Router::new()
.route(
LIVESAFE_PUBLIC_ADAPTER_OUTPUT_AUTHORIZATION_ROUTE,
post(|| async { "public-output" }),
)
.route("/api/v1/avc/issue", post(|| async { "issue" }))
.route("/api/v1/receipts/:hash", get(|| async { "receipt" }))
.layer(middleware::from_fn(move |req, next| {
let a = auth.clone();
let scoped = scoped_auth.clone();
require_bearer_on_writes_with_scoped_bearers(a, scoped, req, next)
}))
}
fn crosschecked_scoped_bearer_test_app() -> Router {
let auth = test_auth();
let scoped_auth = ScopedBearerAuth::none().with_crosschecked_anchor(
CrossCheckedBearerVerifier::from_bearer(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)
.unwrap(),
);
Router::new()
.route("/api/v1/anchors/crosschecked", post(|| async { "anchor" }))
.route(
"/api/v1/anchors/crosschecked/:action_hash",
get(|| async { "readback" }),
)
.route("/write", post(|| async { "write" }))
.layer(middleware::from_fn(move |req, next| {
let auth = auth.clone();
let scoped = scoped_auth.clone();
require_bearer_on_writes_with_scoped_bearers(auth, scoped, req, next)
}))
}
fn strict_test_app() -> Router {
let auth = test_auth();
Router::new()
.route("/read", get(|| async { "ok" }))
.layer(middleware::from_fn(move |req, next| {
let a = auth.clone();
require_bearer(a, req, next)
}))
}
#[tokio::test]
async fn strict_get_without_token_rejected() {
let app = strict_test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/read")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn strict_get_with_correct_token_passes() {
let app = strict_test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/read")
.header("authorization", "Bearer test-token-abc123")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn livesafe_public_output_scoped_bearer_accepts_exact_public_output_route_when_configured()
{
let app = livesafe_public_output_scoped_bearer_test_app(Some(
"livesafe-public-output-scoped-token",
));
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/avc/livesafe/public-adapter-output-authorization")
.header(
"Authorization",
"Bearer livesafe-public-output-scoped-token",
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn livesafe_public_output_scoped_bearer_does_not_authorize_avc_issue() {
let app = livesafe_public_output_scoped_bearer_test_app(Some(
"livesafe-public-output-scoped-token",
));
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/avc/issue")
.header(
"Authorization",
"Bearer livesafe-public-output-scoped-token",
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn livesafe_public_output_scoped_bearer_does_not_authorize_sensitive_receipt_read() {
let app = livesafe_public_output_scoped_bearer_test_app(Some(
"livesafe-public-output-scoped-token",
));
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/receipts/0000000000000000000000000000000000000000000000000000000000000000")
.header(
"Authorization",
"Bearer livesafe-public-output-scoped-token",
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn livesafe_public_output_scoped_bearer_rejects_wrong_token_on_exact_route() {
let app = livesafe_public_output_scoped_bearer_test_app(Some(
"livesafe-public-output-scoped-token",
));
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/avc/livesafe/public-adapter-output-authorization")
.header("Authorization", "Bearer wrong-livesafe-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn livesafe_public_output_scoped_bearer_absent_preserves_admin_only_behavior() {
let admin_app = livesafe_public_output_scoped_bearer_test_app(None);
let admin_resp = admin_app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/avc/livesafe/public-adapter-output-authorization")
.header("Authorization", "Bearer test-token-abc123")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(admin_resp.status(), StatusCode::OK);
let scoped_app = livesafe_public_output_scoped_bearer_test_app(None);
let scoped_resp = scoped_app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/avc/livesafe/public-adapter-output-authorization")
.header(
"Authorization",
"Bearer livesafe-public-output-scoped-token",
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(scoped_resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn crosschecked_scoped_bearer_is_exact_route_only_and_rejects_admin() {
for (method, uri) in [
("POST", "/api/v1/anchors/crosschecked"),
(
"GET",
"/api/v1/anchors/crosschecked/5353535353535353535353535353535353535353535353535353535353535353",
),
] {
let accepted = crosschecked_scoped_bearer_test_app()
.oneshot(
Request::builder()
.method(method)
.uri(uri)
.header(
"Authorization",
"Bearer 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(accepted.status(), StatusCode::OK);
let admin = crosschecked_scoped_bearer_test_app()
.oneshot(
Request::builder()
.method(method)
.uri(uri)
.header("Authorization", "Bearer test-token-abc123")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(admin.status(), StatusCode::FORBIDDEN);
}
let escaped = crosschecked_scoped_bearer_test_app()
.oneshot(
Request::builder()
.method("POST")
.uri("/write")
.header(
"Authorization",
"Bearer 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(escaped.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn get_requests_pass_without_token() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/read")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn receipt_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/receipts/0000000000000000000000000000000000000000000000000000000000000000")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn receipt_list_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/receipts?actor=did:exo:alice")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn provenance_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/provenance/0000000000000000000000000000000000000000000000000000000000000000")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn avc_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/avc/0000000000000000000000000000000000000000000000000000000000000000")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn agent_avc_list_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/agents/did:exo:alice/avcs")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn challenge_list_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/challenges")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn challenge_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/challenges/00000000-0000-0000-0000-000000000000")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn exoforge_dashboard_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/exoforge")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn exoforge_tasks_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/forge/tasks")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn exoforge_stats_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/forge/stats")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn exoforge_activity_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/forge/activity")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn exoforge_read_get_with_correct_token_passes() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/forge/tasks")
.header("authorization", "Bearer test-token-abc123")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn sensitive_read_paths_include_exoforge_surfaces() {
assert!(is_sensitive_read_path("/exoforge"));
assert!(is_sensitive_read_path("/api/v1/forge/tasks"));
assert!(is_sensitive_read_path("/api/v1/forge/stats"));
assert!(is_sensitive_read_path("/api/v1/forge/activity"));
}
#[test]
fn authority_evidence_reads_require_bearer_authentication() {
assert!(is_sensitive_read_path("/api/v1/authority/pack"));
assert!(is_sensitive_read_path(
"/api/v1/authority/evidence/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
));
assert!(is_sensitive_read_path(
"/api/v1/authority/evidence/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/verify"
));
}
#[tokio::test]
async fn economy_trust_object_get_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/economy/bailment-terms/0000000000000000000000000000000000000000000000000000000000000000")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn economy_active_policy_get_without_token_passes() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/economy/policy/active")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn sensitive_get_with_correct_token_passes() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/v1/provenance/0000000000000000000000000000000000000000000000000000000000000000")
.header("Authorization", "Bearer test-token-abc123")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn post_without_token_rejected() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/write")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn post_with_wrong_token_forbidden() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/write")
.header("Authorization", "Bearer wrong-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn post_with_correct_token_passes() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/write")
.header("Authorization", "Bearer test-token-abc123")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn zerodentity_attest_post_with_identity_session_bearer_reaches_local_signed_verifier() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/0dentity/did:exo:alice/attest")
.header("Authorization", "Bearer identity-session-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn zerodentity_delete_with_identity_session_bearer_reaches_local_signed_verifier() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("DELETE")
.uri("/api/v1/0dentity/did:exo:alice")
.header("Authorization", "Bearer identity-session-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn unknown_zerodentity_write_still_requires_admin_bearer() {
let app = test_app();
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/0dentity/did:exo:alice/score")
.header("Authorization", "Bearer identity-session-token")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn token_generation_is_unique() {
let t1 = generate_admin_token().expect("admin token generation");
let t2 = generate_admin_token().expect("admin token generation");
assert_ne!(t1, t2);
assert_eq!(t1.len(), 64); }
#[test]
fn token_generation_propagates_entropy_failure_without_panic() {
let err = generate_admin_token_with_entropy(|_| Err("entropy unavailable"))
.expect_err("entropy failure must propagate");
assert_eq!(err, "entropy unavailable");
}
#[test]
fn admin_token_writer_replaces_existing_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("admin_token");
write_admin_token_file(&path, "old-token").unwrap();
write_admin_token_file(&path, "new-token").unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "new-token");
assert!(!path.with_extension("tmp").exists());
}
#[cfg(unix)]
#[test]
fn admin_token_writer_creates_owner_only_file() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("admin_token");
write_admin_token_file(&path, "secret-token").unwrap();
let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o600);
assert_eq!(std::fs::read_to_string(&path).unwrap(), "secret-token");
}
#[cfg(unix)]
#[test]
fn private_file_admin_writer_refuses_permissive_destination_and_stale_temp() {
use std::os::unix::fs::PermissionsExt;
let permissive_destination = tempfile::tempdir().unwrap();
let path = permissive_destination.path().join("admin_token");
std::fs::write(&path, "legacy-token").unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
assert!(write_admin_token_file(&path, "new-token").is_err());
assert_eq!(std::fs::read_to_string(&path).unwrap(), "legacy-token");
let permissive_temp = tempfile::tempdir().unwrap();
let path = permissive_temp.path().join("admin_token");
let temp = crate::private_file::private_temp_path(&path);
std::fs::write(&temp, "attacker temp").unwrap();
std::fs::set_permissions(&temp, std::fs::Permissions::from_mode(0o644)).unwrap();
assert!(write_admin_token_file(&path, "new-token").is_err());
assert_eq!(std::fs::read_to_string(&temp).unwrap(), "attacker temp");
assert!(!path.exists());
}
#[test]
fn bearer_token_verifier_matches_equal() {
assert!(BearerTokenVerifier::from_bearer("abcdef").verifies("abcdef"));
assert!(BearerTokenVerifier::from_bearer("").verifies(""));
}
#[test]
fn bearer_token_verifier_rejects_different() {
let verifier = BearerTokenVerifier::from_bearer("abcdef");
assert!(!verifier.verifies("abcdeg"));
assert!(!verifier.verifies("different-length"));
assert!(!verifier.verifies(""));
}
#[test]
fn bearer_token_verifier_distinguishes_byte_differences() {
let verifier = BearerTokenVerifier::from_bearer("abcdef");
assert!(!verifier.verifies("xbcdef"));
assert!(!verifier.verifies("abcdex"));
assert!(!verifier.verifies("xxxxxx"));
}
#[test]
fn production_source_uses_zeroizing_admin_token_storage() {
let source = include_str!("auth.rs");
let production = source
.split("// ---------------------------------------------------------------------------\n// Tests")
.next()
.expect("tests marker present");
assert!(
production.contains("Zeroizing<String>"),
"bearer admin token must be stored in zeroize-on-drop storage"
);
assert!(
!production.contains("Arc<String>"),
"bearer admin token must not be held in a plain Arc<String>"
);
assert!(
production.contains("bytes.zeroize()"),
"raw random token bytes must be wiped after hex encoding"
);
assert!(
!production.contains("expect(\"OS entropy source unavailable\")"),
"admin token generation must propagate entropy failures instead of panicking"
);
assert!(
production.contains(
"pub fn generate_admin_token() -> Result<Zeroizing<String>, getrandom::Error>"
),
"admin token generation must return a typed entropy error"
);
assert!(
!production.contains("let _ = std::fs::remove_file(&tmp_path)"),
"temporary admin token cleanup failures must propagate"
);
}
#[test]
fn main_persists_admin_token_through_restrictive_auth_writer() {
let source = include_str!("main.rs");
let production = source
.split("#[cfg(test)]")
.next()
.expect("tests marker present");
assert!(
production.contains("auth::write_admin_token_file"),
"startup must persist admin tokens through the restrictive auth writer"
);
assert!(
!production.contains("std::fs::write(&token_path, &admin_token)"),
"startup must not write the admin token before restrictive permissions are set"
);
}
#[test]
fn startup_drops_raw_admin_token_after_final_configuration_use() {
let source = include_str!("main.rs");
let production = source
.split("#[cfg(test)]")
.next()
.expect("tests marker present");
let startup = production
.split("async fn start_node(")
.nth(1)
.expect("start_node implementation present");
let crosschecked_configuration = startup
.find("let crosschecked_anchor_config =")
.expect("CrossChecked startup configuration local present");
let configuration_completion = crosschecked_configuration
+ startup[crosschecked_configuration..]
.find(';')
.expect("CrossChecked startup configuration call completes")
+ 1;
let configuration_statement =
&startup[crosschecked_configuration..configuration_completion];
let drop_position = configuration_completion
+ startup[configuration_completion..]
.find("drop(admin_token);")
.expect("raw admin token explicitly dropped after startup configuration");
let crosschecked_router_match = startup
.find("let crosschecked_anchor_router = match crosschecked_anchor_config")
.expect("CrossChecked router construction uses the evaluated configuration");
let first_await_after_configuration = configuration_completion
+ startup[configuration_completion..]
.find(".await")
.expect("startup contains an await after CrossChecked configuration");
assert!(
configuration_statement
.contains("crosschecked_anchor_startup_config_from_environment(")
&& configuration_statement.contains("admin_token.as_str()"),
"CrossChecked configuration must consume the raw admin token in the evaluated local"
);
assert!(
configuration_completion < drop_position,
"raw admin token must be dropped only after CrossChecked configuration evaluation completes"
);
assert!(
drop_position < crosschecked_router_match,
"raw admin token must be dropped before CrossChecked router matching and construction"
);
assert!(
drop_position < first_await_after_configuration,
"raw admin token must be dropped before the first await after CrossChecked configuration"
);
assert!(
startup_drops_admin_token_before_first_await_after_configuration(startup),
"startup token-drop ordering predicate must accept the production sequence"
);
assert!(
crosschecked_router_match < first_await_after_configuration,
"CrossChecked router matching must precede the first await after configuration"
);
assert!(
startup[crosschecked_router_match..=first_await_after_configuration]
.contains("postgres_crosschecked_anchor_clock("),
"the first CrossChecked router await must be the PostgreSQL anchor clock"
);
assert!(
!startup[drop_position + "drop(admin_token);".len()..].contains("admin_token.as_str()"),
"startup must not use raw admin token material after explicitly dropping it"
);
}
#[test]
fn startup_token_drop_order_rejects_await_before_drop() {
let mutated_startup = r#"
let crosschecked_anchor_config =
crosschecked_anchor_startup_config_from_environment(admin_token.as_str())?;
readiness_probe().await?;
drop(admin_token);
"#;
assert!(
!startup_drops_admin_token_before_first_await_after_configuration(mutated_startup),
"an await inserted after CrossChecked configuration but before raw-token destruction must be rejected"
);
}
#[test]
fn startup_does_not_log_admin_token_material() {
let main_source = include_str!("main.rs");
let main_production = main_source
.split("#[cfg(test)]")
.next()
.expect("tests marker present");
let auth_source = include_str!("auth.rs");
let auth_production = auth_source
.split("// ---------------------------------------------------------------------------\n// Tests")
.next()
.expect("tests marker present");
assert!(
!main_production.contains("token_prefix"),
"startup logs must not include even partial admin bearer token material"
);
assert!(
!main_production.contains("admin_token.chars().take"),
"startup must not derive loggable substrings from the admin bearer token"
);
assert!(
!auth_production.contains("displayed once"),
"auth documentation must not normalize logging bearer-token material"
);
}
#[test]
fn livesafe_public_output_scoped_bearer_startup_guard_wires_env_without_logging_material() {
let main_source = include_str!("main.rs");
let main_production = main_source
.split("#[cfg(test)]")
.next()
.expect("tests marker present");
let auth_source = include_str!("auth.rs");
let auth_production = auth_source
.split("// ---------------------------------------------------------------------------\n// Tests")
.next()
.expect("tests marker present");
assert!(
main_production
.contains("EXOCHAIN_LIVESAFE_PUBLIC_ADAPTER_OUTPUT_AUTHORIZATION_BEARER"),
"startup must read the scoped LiveSafe public-output bearer env var"
);
assert!(
auth_production.contains("/api/v1/avc/livesafe/public-adapter-output-authorization"),
"auth middleware must bind the scoped bearer to the exact public-output route"
);
assert!(
!main_production.contains("livesafe-public-output-scoped-token"),
"startup source must not embed scoped bearer material"
);
assert!(
!auth_production.contains("livesafe-public-output-scoped-token"),
"auth source must not embed scoped bearer material"
);
assert!(
!main_production.contains("scoped_token.chars().take"),
"startup logs must not include even partial scoped bearer token material"
);
}
}