VYPR
High severityNVD Advisory· Published Feb 3, 2026· Updated Feb 3, 2026

RustFS sourceIp bypass via spoofed X-Forwarded-For/Real-IP headers

CVE-2026-21862

Description

RustFS is a distributed object storage system built in Rust. Prior to version alpha.78, IP-based access control can be bypassed: get_condition_values trusts client-supplied X-Forwarded-For/X-Real-Ip without verifying a trusted proxy, so any reachable client can spoof aws:SourceIp and satisfy IP-allowlist policies. This issue has been patched in version alpha.78.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
rustfscrates.io
< 1.0.0-alpha.781.0.0-alpha.78

Affected products

1

Patches

1
b4ba62fa3300

fix: correctly handle aws:SourceIp in policy evaluation (#1301) (#1306)

https://github.com/rustfs/rustfsloverustfsDec 30, 2025via ghsa
19 files changed · +371 37
  • rustfs/Cargo.toml+1 1 modified
    @@ -82,7 +82,7 @@ tokio-stream.workspace = true
     tokio-util.workspace = true
     tonic = { workspace = true }
     tower.workspace = true
    -tower-http = { workspace = true, features = ["trace", "compression-full", "cors", "catch-panic", "timeout", "limit", "request-id"] }
    +tower-http = { workspace = true, features = ["trace", "compression-full", "cors", "catch-panic", "timeout", "limit", "request-id", "add-extension"] }
     
     # Serialization and Data Formats
     bytes = { workspace = true }
    
  • rustfs/src/admin/auth.rs+4 2 modified
    @@ -30,12 +30,13 @@ pub async fn validate_admin_request(
         is_owner: bool,
         deny_only: bool,
         actions: Vec<Action>,
    +    remote_addr: Option<std::net::SocketAddr>,
     ) -> S3Result<()> {
         let Ok(iam_store) = rustfs_iam::get() else {
             return Err(s3_error!(InternalError, "iam not init"));
         };
         for action in actions {
    -        match check_admin_request_auth(iam_store.clone(), headers, cred, is_owner, deny_only, action).await {
    +        match check_admin_request_auth(iam_store.clone(), headers, cred, is_owner, deny_only, action, remote_addr).await {
                 Ok(_) => return Ok(()),
                 Err(_) => {
                     continue;
    @@ -53,8 +54,9 @@ async fn check_admin_request_auth(
         is_owner: bool,
         deny_only: bool,
         action: Action,
    +    remote_addr: Option<std::net::SocketAddr>,
     ) -> S3Result<()> {
    -    let conditions = get_condition_values(headers, cred, None, None);
    +    let conditions = get_condition_values(headers, cred, None, None, remote_addr);
     
         if !iam_store
             .is_allowed(&Args {
    
  • rustfs/src/admin/handlers/bucket_meta.rs+3 0 modified
    @@ -20,6 +20,7 @@ use std::{
     use crate::{
         admin::{auth::validate_admin_request, router::Operation},
         auth::{check_key_valid, get_session_token},
    +    server::RemoteAddr,
     };
     use http::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -97,6 +98,7 @@ impl Operation for ExportBucketMetadata {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ExportBucketMetadataAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -389,6 +391,7 @@ impl Operation for ImportBucketMetadata {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ImportBucketMetadataAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/group.rs+5 0 modified
    @@ -15,6 +15,7 @@
     use crate::{
         admin::{auth::validate_admin_request, router::Operation, utils::has_space_be},
         auth::{check_key_valid, constant_time_eq, get_session_token},
    +    server::RemoteAddr,
     };
     use http::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -57,6 +58,7 @@ impl Operation for ListGroups {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ListGroupsAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -95,6 +97,7 @@ impl Operation for GetGroup {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::GetGroupAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -142,6 +145,7 @@ impl Operation for SetGroupStatus {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::EnableGroupAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -209,6 +213,7 @@ impl Operation for UpdateGroupMembers {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::AddUserToGroupAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/kms_dynamic.rs+6 0 modified
    @@ -17,6 +17,7 @@
     use super::Operation;
     use crate::admin::auth::validate_admin_request;
     use crate::auth::{check_key_valid, get_session_token};
    +use crate::server::RemoteAddr;
     use hyper::StatusCode;
     use matchit::Params;
     use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
    @@ -98,6 +99,7 @@ impl Operation for ConfigureKmsHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -196,6 +198,7 @@ impl Operation for StartKmsHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -329,6 +332,7 @@ impl Operation for StopKmsHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -394,6 +398,7 @@ impl Operation for GetKmsStatusHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -465,6 +470,7 @@ impl Operation for ReconfigureKmsHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/kms_keys.rs+6 0 modified
    @@ -17,6 +17,7 @@
     use super::Operation;
     use crate::admin::auth::validate_admin_request;
     use crate::auth::{check_key_valid, get_session_token};
    +use crate::server::RemoteAddr;
     use hyper::{HeaderMap, StatusCode};
     use matchit::Params;
     use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
    @@ -79,6 +80,7 @@ impl Operation for CreateKmsKeyHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -212,6 +214,7 @@ impl Operation for DeleteKmsKeyHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -360,6 +363,7 @@ impl Operation for CancelKmsKeyDeletionHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -488,6 +492,7 @@ impl Operation for ListKmsKeysHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -599,6 +604,7 @@ impl Operation for DescribeKmsKeyHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/kms.rs+8 0 modified
    @@ -17,6 +17,7 @@
     use super::Operation;
     use crate::admin::auth::validate_admin_request;
     use crate::auth::{check_key_valid, get_session_token};
    +use crate::server::RemoteAddr;
     use base64::Engine;
     use hyper::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -127,6 +128,7 @@ impl Operation for CreateKeyHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], // TODO: Add specific KMS action
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -205,6 +207,7 @@ impl Operation for DescribeKeyHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -260,6 +263,7 @@ impl Operation for ListKeysHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -321,6 +325,7 @@ impl Operation for GenerateDataKeyHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -386,6 +391,7 @@ impl Operation for KmsStatusHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -443,6 +449,7 @@ impl Operation for KmsConfigHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -487,6 +494,7 @@ impl Operation for KmsClearCacheHandler {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/policies.rs+6 0 modified
    @@ -15,6 +15,7 @@
     use crate::{
         admin::{auth::validate_admin_request, router::Operation, utils::has_space_be},
         auth::{check_key_valid, get_session_token},
    +    server::RemoteAddr,
     };
     use http::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -60,6 +61,7 @@ impl Operation for ListCannedPolicies {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ListUserPoliciesAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -118,6 +120,7 @@ impl Operation for AddCannedPolicy {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::CreatePolicyAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -190,6 +193,7 @@ impl Operation for InfoCannedPolicy {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::GetPolicyAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -247,6 +251,7 @@ impl Operation for RemoveCannedPolicy {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::DeletePolicyAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -307,6 +312,7 @@ impl Operation for SetPolicyForUserOrGroup {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::AttachPolicyAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/pools.rs+5 0 modified
    @@ -26,6 +26,7 @@ use crate::{
         admin::{auth::validate_admin_request, router::Operation},
         auth::{check_key_valid, get_session_token},
         error::ApiError,
    +    server::RemoteAddr,
     };
     
     pub struct ListPools {}
    @@ -53,6 +54,7 @@ impl Operation for ListPools {
                     Action::AdminAction(AdminAction::ServerInfoAdminAction),
                     Action::AdminAction(AdminAction::DecommissionAdminAction),
                 ],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -119,6 +121,7 @@ impl Operation for StatusPool {
                     Action::AdminAction(AdminAction::ServerInfoAdminAction),
                     Action::AdminAction(AdminAction::DecommissionAdminAction),
                 ],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -194,6 +197,7 @@ impl Operation for StartDecommission {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::DecommissionAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -292,6 +296,7 @@ impl Operation for CancelDecommission {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::DecommissionAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/rebalance.rs+4 0 modified
    @@ -15,6 +15,7 @@
     use crate::{
         admin::{auth::validate_admin_request, router::Operation},
         auth::{check_key_valid, get_session_token},
    +    server::RemoteAddr,
     };
     use http::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -103,6 +104,7 @@ impl Operation for RebalanceStart {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::RebalanceAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -180,6 +182,7 @@ impl Operation for RebalanceStatus {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::RebalanceAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -297,6 +300,7 @@ impl Operation for RebalanceStop {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::RebalanceAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    
  • rustfs/src/admin/handlers.rs+9 1 modified
    @@ -18,6 +18,7 @@ use crate::auth::check_key_valid;
     use crate::auth::get_condition_values;
     use crate::auth::get_session_token;
     use crate::error::ApiError;
    +use crate::server::RemoteAddr;
     use bytes::Bytes;
     use futures::{Stream, StreamExt};
     use http::{HeaderMap, HeaderValue, Uri};
    @@ -210,7 +211,8 @@ impl Operation for AccountInfoHandler {
             let claims = cred.claims.as_ref().unwrap_or(&default_claims);
     
             let cred_clone = cred.clone();
    -        let conditions = get_condition_values(&req.headers, &cred_clone, None, None);
    +        let remote_addr = req.extensions.get::<RemoteAddr>().map(|a| a.0);
    +        let conditions = get_condition_values(&req.headers, &cred_clone, None, None, remote_addr);
             let cred_clone = Arc::new(cred_clone);
             let conditions = Arc::new(conditions);
     
    @@ -405,12 +407,14 @@ impl Operation for ServerInfoHandler {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    +        let remote_addr = req.extensions.get::<RemoteAddr>().map(|a| a.0);
             validate_admin_request(
                 &req.headers,
                 &cred,
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
    +            remote_addr,
             )
             .await?;
     
    @@ -451,12 +455,14 @@ impl Operation for StorageInfoHandler {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    +        let remote_addr = req.extensions.get::<RemoteAddr>().map(|a| a.0);
             validate_admin_request(
                 &req.headers,
                 &cred,
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::StorageInfoAdminAction)],
    +            remote_addr,
             )
             .await?;
     
    @@ -492,6 +498,7 @@ impl Operation for DataUsageInfoHandler {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    +        let remote_addr = req.extensions.get::<RemoteAddr>().map(|a| a.0);
             validate_admin_request(
                 &req.headers,
                 &cred,
    @@ -501,6 +508,7 @@ impl Operation for DataUsageInfoHandler {
                     Action::AdminAction(AdminAction::DataUsageInfoAdminAction),
                     Action::S3Action(S3Action::ListBucketAction),
                 ],
    +            remote_addr,
             )
             .await?;
     
    
  • rustfs/src/admin/handlers/service_account.rs+36 5 modified
    @@ -14,6 +14,7 @@
     
     use crate::admin::utils::has_space_be;
     use crate::auth::{constant_time_eq, get_condition_values, get_session_token};
    +use crate::server::RemoteAddr;
     use crate::{admin::router::Operation, auth::check_key_valid};
     use http::HeaderMap;
     use hyper::StatusCode;
    @@ -119,7 +120,13 @@ impl Operation for AddServiceAccount {
                     groups: &cred.groups,
                     action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction),
                     bucket: "",
    -                conditions: &get_condition_values(&req.headers, &cred, None, None),
    +                conditions: &get_condition_values(
    +                    &req.headers,
    +                    &cred,
    +                    None,
    +                    None,
    +                    req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +                ),
                     is_owner: owner,
                     object: "",
                     claims: cred.claims.as_ref().unwrap_or(&HashMap::new()),
    @@ -270,7 +277,13 @@ impl Operation for UpdateServiceAccount {
                     groups: &cred.groups,
                     action: Action::AdminAction(AdminAction::UpdateServiceAccountAdminAction),
                     bucket: "",
    -                conditions: &get_condition_values(&req.headers, &cred, None, None),
    +                conditions: &get_condition_values(
    +                    &req.headers,
    +                    &cred,
    +                    None,
    +                    None,
    +                    req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +                ),
                     is_owner: owner,
                     object: "",
                     claims: cred.claims.as_ref().unwrap_or(&HashMap::new()),
    @@ -363,7 +376,13 @@ impl Operation for InfoServiceAccount {
                     groups: &cred.groups,
                     action: Action::AdminAction(AdminAction::ListServiceAccountsAdminAction),
                     bucket: "",
    -                conditions: &get_condition_values(&req.headers, &cred, None, None),
    +                conditions: &get_condition_values(
    +                    &req.headers,
    +                    &cred,
    +                    None,
    +                    None,
    +                    req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +                ),
                     is_owner: owner,
                     object: "",
                     claims: cred.claims.as_ref().unwrap_or(&HashMap::new()),
    @@ -491,7 +510,13 @@ impl Operation for ListServiceAccount {
                         groups: &cred.groups,
                         action: Action::AdminAction(AdminAction::UpdateServiceAccountAdminAction),
                         bucket: "",
    -                    conditions: &get_condition_values(&req.headers, &cred, None, None),
    +                    conditions: &get_condition_values(
    +                        &req.headers,
    +                        &cred,
    +                        None,
    +                        None,
    +                        req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +                    ),
                         is_owner: owner,
                         object: "",
                         claims: cred.claims.as_ref().unwrap_or(&HashMap::new()),
    @@ -589,7 +614,13 @@ impl Operation for DeleteServiceAccount {
                     groups: &cred.groups,
                     action: Action::AdminAction(AdminAction::RemoveServiceAccountAdminAction),
                     bucket: "",
    -                conditions: &get_condition_values(&req.headers, &cred, None, None),
    +                conditions: &get_condition_values(
    +                    &req.headers,
    +                    &cred,
    +                    None,
    +                    None,
    +                    req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +                ),
                     is_owner: owner,
                     object: "",
                     claims: cred.claims.as_ref().unwrap_or(&HashMap::new()),
    
  • rustfs/src/admin/handlers/tier.rs+64 7 modified
    @@ -16,6 +16,7 @@
     use crate::{
         admin::{auth::validate_admin_request, router::Operation},
         auth::{check_key_valid, get_session_token},
    +    server::RemoteAddr,
     };
     use http::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -90,7 +91,15 @@ impl Operation for AddTier {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::SetTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut input = req.input;
             let body = match input.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE).await {
    @@ -218,7 +227,15 @@ impl Operation for EditTier {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::SetTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut input = req.input;
             let body = match input.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE).await {
    @@ -293,7 +310,15 @@ impl Operation for ListTiers {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::ListTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut tier_config_mgr = GLOBAL_TierConfigMgr.read().await;
             let tiers = tier_config_mgr.list_tiers();
    @@ -329,7 +354,15 @@ impl Operation for RemoveTier {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::SetTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut force: bool = false;
             let force_str = query.force.clone().unwrap_or_default();
    @@ -392,7 +425,15 @@ impl Operation for VerifyTier {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::ListTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut tier_config_mgr = GLOBAL_TierConfigMgr.write().await;
             tier_config_mgr.verify(&query.tier.unwrap()).await;
    @@ -415,7 +456,15 @@ impl Operation for GetTierInfo {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ListTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::ListTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let query = {
                 if let Some(query) = req.uri.query() {
    @@ -467,7 +516,15 @@ impl Operation for ClearTier {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::SetTierAction)]).await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::SetTierAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut force: bool = false;
             let force_str = query.force;
    
  • rustfs/src/admin/handlers/user.rs+24 4 modified
    @@ -15,6 +15,7 @@
     use crate::{
         admin::{auth::validate_admin_request, router::Operation, utils::has_space_be},
         auth::{check_key_valid, constant_time_eq, get_session_token},
    +    server::RemoteAddr,
     };
     use http::{HeaderMap, StatusCode};
     use matchit::Params;
    @@ -124,6 +125,7 @@ impl Operation for AddUser {
                 owner,
                 deny_only,
                 vec![Action::AdminAction(AdminAction::CreateUserAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -176,6 +178,7 @@ impl Operation for SetUserStatus {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::EnableUserAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -220,6 +223,7 @@ impl Operation for ListUsers {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::ListUsersAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -278,6 +282,7 @@ impl Operation for RemoveUser {
                 owner,
                 false,
                 vec![Action::AdminAction(AdminAction::DeleteUserAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -377,6 +382,7 @@ impl Operation for GetUserInfo {
                 owner,
                 deny_only,
                 vec![Action::AdminAction(AdminAction::GetUserAdminAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
             )
             .await?;
     
    @@ -426,8 +432,15 @@ impl Operation for ExportIam {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ExportIAMAction)])
    -            .await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::ExportIAMAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let Ok(iam_store) = rustfs_iam::get() else {
                 return Err(s3_error!(InvalidRequest, "iam not init"));
    @@ -633,8 +646,15 @@ impl Operation for ImportIam {
             let (cred, owner) =
                 check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &input_cred.access_key).await?;
     
    -        validate_admin_request(&req.headers, &cred, owner, false, vec![Action::AdminAction(AdminAction::ExportIAMAction)])
    -            .await?;
    +        validate_admin_request(
    +            &req.headers,
    +            &cred,
    +            owner,
    +            false,
    +            vec![Action::AdminAction(AdminAction::ExportIAMAction)],
    +            req.extensions.get::<RemoteAddr>().map(|a| a.0),
    +        )
    +        .await?;
     
             let mut input = req.input;
             let body = match input.store_all_limited(MAX_IAM_IMPORT_SIZE).await {
    
  • rustfs/src/auth.rs+166 14 modified
    @@ -241,6 +241,7 @@ pub fn get_session_token<'a>(uri: &'a Uri, hds: &'a HeaderMap) -> Option<&'a str
     /// * `cred` - User credentials
     /// * `version_id` - Optional version ID of the object
     /// * `region` - Optional region/location constraint
    +/// * `remote_addr` - Optional remote address of the connection
     ///
     /// # Returns
     /// * `HashMap<String, Vec<String>>` - Condition values for policy evaluation
    @@ -250,6 +251,7 @@ pub fn get_condition_values(
         cred: &Credentials,
         version_id: Option<&str>,
         region: Option<&str>,
    +    remote_addr: Option<std::net::SocketAddr>,
     ) -> HashMap<String, Vec<String>> {
         let username = if cred.is_temp() || cred.is_service_account() {
             cred.parent_user.clone()
    @@ -297,20 +299,15 @@ pub fn get_condition_values(
             .unwrap_or(false);
     
         // Get remote address from header or use default
    -    let remote_addr = header
    -        .get("x-forwarded-for")
    -        .and_then(|v| v.to_str().ok())
    -        .and_then(|s| s.split(',').next())
    -        .or_else(|| header.get("x-real-ip").and_then(|v| v.to_str().ok()))
    -        .unwrap_or("127.0.0.1");
    +    let remote_addr_s = remote_addr.map(|a| a.ip().to_string()).unwrap_or_default();
     
         let mut args = HashMap::new();
     
         // Add basic time and security info
         args.insert("CurrentTime".to_owned(), vec![curr_time.format(&Rfc3339).unwrap_or_default()]);
         args.insert("EpochTime".to_owned(), vec![epoch_time.to_string()]);
         args.insert("SecureTransport".to_owned(), vec![is_tls.to_string()]);
    -    args.insert("SourceIp".to_owned(), vec![get_source_ip_raw(header, remote_addr)]);
    +    args.insert("SourceIp".to_owned(), vec![get_source_ip_raw(header, &remote_addr_s)]);
     
         // Add user agent and referer
         if let Some(user_agent) = header.get("user-agent") {
    @@ -848,7 +845,7 @@ mod tests {
             let cred = create_test_credentials();
             let headers = HeaderMap::new();
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(conditions.get("userid"), Some(&vec!["test-access-key".to_string()]));
             assert_eq!(conditions.get("username"), Some(&vec!["test-access-key".to_string()]));
    @@ -860,7 +857,7 @@ mod tests {
             let cred = create_temp_credentials();
             let headers = HeaderMap::new();
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(conditions.get("userid"), Some(&vec!["parent-user".to_string()]));
             assert_eq!(conditions.get("username"), Some(&vec!["parent-user".to_string()]));
    @@ -872,7 +869,7 @@ mod tests {
             let cred = create_service_account_credentials();
             let headers = HeaderMap::new();
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(conditions.get("userid"), Some(&vec!["service-parent".to_string()]));
             assert_eq!(conditions.get("username"), Some(&vec!["service-parent".to_string()]));
    @@ -887,7 +884,7 @@ mod tests {
             headers.insert("x-amz-object-lock-mode", HeaderValue::from_static("GOVERNANCE"));
             headers.insert("x-amz-object-lock-retain-until-date", HeaderValue::from_static("2024-12-31T23:59:59Z"));
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(conditions.get("object-lock-mode"), Some(&vec!["GOVERNANCE".to_string()]));
             assert_eq!(
    @@ -902,7 +899,7 @@ mod tests {
             let mut headers = HeaderMap::new();
             headers.insert("x-amz-signature-age", HeaderValue::from_static("300"));
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(conditions.get("signatureAge"), Some(&vec!["300".to_string()]));
             // Verify the header is removed after processing
    @@ -919,7 +916,7 @@ mod tests {
     
             let headers = HeaderMap::new();
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(conditions.get("username"), Some(&vec!["ldap-user".to_string()]));
             assert_eq!(conditions.get("groups"), Some(&vec!["group1".to_string(), "group2".to_string()]));
    @@ -932,7 +929,7 @@ mod tests {
     
             let headers = HeaderMap::new();
     
    -        let conditions = get_condition_values(&headers, &cred, None, None);
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
     
             assert_eq!(
                 conditions.get("groups"),
    @@ -1208,4 +1205,159 @@ mod tests {
             assert!(constant_time_eq(key1, key2));
             assert!(!constant_time_eq(key1, key3));
         }
    +
    +    #[test]
    +    fn test_get_condition_values_source_ip() {
    +        let mut headers = HeaderMap::new();
    +        let cred = Credentials::default();
    +
    +        // Case 1: No headers, no remote addr -> empty string
    +        let conditions = get_condition_values(&headers, &cred, None, None, None);
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "");
    +
    +        // Case 2: No headers, with remote addr -> remote addr
    +        let remote_addr: std::net::SocketAddr = "192.168.0.10:12345".parse().unwrap();
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
    +
    +        // Case 3: X-Forwarded-For present -> XFF (takes precedence over remote_addr)
    +        headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.1"));
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.1");
    +
    +        // Case 4: X-Forwarded-For with multiple IPs -> First IP
    +        headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.3, 10.0.0.4"));
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.3");
    +
    +        // Case 5: X-Real-IP present (XFF removed) -> X-Real-IP
    +        headers.remove("x-forwarded-for");
    +        headers.insert("x-real-ip", HeaderValue::from_static("10.0.0.2"));
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.2");
    +
    +        // Case 6: Forwarded header present (X-Real-IP removed) -> Forwarded
    +        headers.remove("x-real-ip");
    +        headers.insert("forwarded", HeaderValue::from_static("for=10.0.0.5;proto=http"));
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.5");
    +
    +        // Case 7: Forwarded header with quotes and multiple values
    +        headers.insert("forwarded", HeaderValue::from_static("for=\"10.0.0.6\", for=10.0.0.7"));
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.6");
    +
    +        // Case 8: IPv6 Remote Addr
    +        let remote_addr_v6: std::net::SocketAddr = "[2001:db8::1]:8080".parse().unwrap();
    +        headers.clear();
    +        let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr_v6));
    +        assert_eq!(conditions.get("SourceIp").unwrap()[0], "2001:db8::1");
    +    }
    +}
    +
    +#[cfg(test)]
    +mod tests_policy {
    +    use rustfs_policy::policy::action::{Action, S3Action};
    +    use rustfs_policy::policy::{Args, BucketPolicy, BucketPolicyArgs, Policy};
    +    use std::collections::HashMap;
    +
    +    #[tokio::test]
    +    async fn test_iam_policy_source_ip() {
    +        let policy_json = r#"{
    +            "Version": "2012-10-17",
    +            "Statement": [
    +                {
    +                    "Effect": "Allow",
    +                    "Action": ["s3:GetObject"],
    +                    "Resource": ["arn:aws:s3:::mybucket/*"],
    +                    "Condition": {
    +                        "IpAddress": {
    +                            "aws:SourceIp": "192.168.1.0/24"
    +                        }
    +                    }
    +                }
    +            ]
    +        }"#;
    +
    +        let policy: Policy = serde_json::from_str(policy_json).expect("Failed to parse IAM policy");
    +
    +        // Case 1: Matching IP
    +        let mut conditions = HashMap::new();
    +        conditions.insert("SourceIp".to_string(), vec!["192.168.1.10".to_string()]);
    +
    +        let claims = HashMap::new();
    +        let args = Args {
    +            account: "test-account",
    +            groups: &None,
    +            action: Action::S3Action(S3Action::GetObjectAction),
    +            bucket: "mybucket",
    +            conditions: &conditions,
    +            is_owner: false,
    +            object: "myobject",
    +            claims: &claims,
    +            deny_only: false,
    +        };
    +
    +        assert!(policy.is_allowed(&args).await, "IAM Policy should allow matching IP");
    +
    +        // Case 2: Non-matching IP
    +        let mut conditions_fail = HashMap::new();
    +        conditions_fail.insert("SourceIp".to_string(), vec!["10.0.0.1".to_string()]);
    +
    +        let args_fail = Args {
    +            conditions: &conditions_fail,
    +            ..args
    +        };
    +
    +        assert!(!policy.is_allowed(&args_fail).await, "IAM Policy should deny non-matching IP");
    +    }
    +
    +    #[tokio::test]
    +    async fn test_bucket_policy_source_ip() {
    +        let policy_json = r#"{
    +            "Version": "2012-10-17",
    +            "Statement": [
    +                {
    +                    "Effect": "Allow",
    +                    "Principal": {"AWS": ["*"]},
    +                    "Action": ["s3:GetObject"],
    +                    "Resource": ["arn:aws:s3:::mybucket/*"],
    +                    "Condition": {
    +                        "IpAddress": {
    +                            "aws:SourceIp": "192.168.1.0/24"
    +                        }
    +                    }
    +                }
    +            ]
    +        }"#;
    +
    +        let policy: BucketPolicy = serde_json::from_str(policy_json).expect("Failed to parse Bucket policy");
    +
    +        // Case 1: Matching IP
    +        let mut conditions = HashMap::new();
    +        conditions.insert("SourceIp".to_string(), vec!["192.168.1.10".to_string()]);
    +
    +        let args = BucketPolicyArgs {
    +            account: "test-account",
    +            groups: &None,
    +            action: Action::S3Action(S3Action::GetObjectAction),
    +            bucket: "mybucket",
    +            conditions: &conditions,
    +            is_owner: false,
    +            object: "myobject",
    +        };
    +
    +        assert!(policy.is_allowed(&args).await, "Bucket Policy should allow matching IP");
    +
    +        // Case 2: Non-matching IP
    +        let mut conditions_fail = HashMap::new();
    +        conditions_fail.insert("SourceIp".to_string(), vec!["10.0.0.1".to_string()]);
    +
    +        let args_fail = BucketPolicyArgs {
    +            conditions: &conditions_fail,
    +            ..args
    +        };
    +
    +        assert!(!policy.is_allowed(&args_fail).await, "Bucket Policy should deny non-matching IP");
    +    }
     }
    
  • rustfs/src/server/http.rs+14 1 modified
    @@ -17,7 +17,7 @@ use super::compress::{CompressionConfig, CompressionPredicate};
     use crate::admin;
     use crate::auth::IAMAuth;
     use crate::config;
    -use crate::server::{ReadinessGateLayer, ServiceState, ServiceStateManager, hybrid::hybrid, layer::RedirectLayer};
    +use crate::server::{ReadinessGateLayer, RemoteAddr, ServiceState, ServiceStateManager, hybrid::hybrid, layer::RedirectLayer};
     use crate::storage;
     use crate::storage::tonic_service::make_server;
     use bytes::Bytes;
    @@ -44,6 +44,7 @@ use tokio::net::{TcpListener, TcpStream};
     use tokio_rustls::TlsAcceptor;
     use tonic::{Request, Status, metadata::MetadataValue};
     use tower::ServiceBuilder;
    +use tower_http::add_extension::AddExtensionLayer;
     use tower_http::catch_panic::CatchPanicLayer;
     use tower_http::compression::CompressionLayer;
     use tower_http::cors::{AllowOrigin, Any, CorsLayer};
    @@ -528,9 +529,21 @@ fn process_connection(
             let rpc_service = NodeServiceServer::with_interceptor(make_server(), check_auth);
             let service = hybrid(s3_service, rpc_service);
     
    +        let remote_addr = match socket.peer_addr() {
    +            Ok(addr) => Some(RemoteAddr(addr)),
    +            Err(e) => {
    +                tracing::warn!(
    +                    error = %e,
    +                    "Failed to obtain peer address; policy evaluation may fall back to a default source IP"
    +                );
    +                None
    +            }
    +        };
    +
             let hybrid_service = ServiceBuilder::new()
                 .layer(SetRequestIdLayer::x_request_id(MakeRequestUuid))
                 .layer(CatchPanicLayer::new())
    +            .layer(AddExtensionLayer::new(remote_addr))
                 // CRITICAL: Insert ReadinessGateLayer before business logic
                 // This stops requests from hitting IAMAuth or Storage if they are not ready.
                 .layer(ReadinessGateLayer::new(readiness))
    
  • rustfs/src/server/mod.rs+3 0 modified
    @@ -36,3 +36,6 @@ pub(crate) use service_state::ServiceState;
     pub(crate) use service_state::ServiceStateManager;
     pub(crate) use service_state::ShutdownSignal;
     pub(crate) use service_state::wait_for_shutdown;
    +
    +#[derive(Clone, Copy, Debug)]
    +pub struct RemoteAddr(pub std::net::SocketAddr);
    
  • rustfs/src/storage/access.rs+4 1 modified
    @@ -15,6 +15,7 @@
     use super::ecfs::FS;
     use crate::auth::{check_key_valid, get_condition_values, get_session_token};
     use crate::license::license_check;
    +use crate::server::RemoteAddr;
     use rustfs_ecstore::bucket::policy_sys::PolicySys;
     use rustfs_iam::error::Error as IamError;
     use rustfs_policy::policy::action::{Action, S3Action};
    @@ -36,6 +37,7 @@ pub(crate) struct ReqInfo {
     
     /// Authorizes the request based on the action and credentials.
     pub async fn authorize_request<T>(req: &mut S3Request<T>, action: Action) -> S3Result<()> {
    +    let remote_addr = req.extensions.get::<RemoteAddr>().map(|a| a.0);
         let req_info = req.extensions.get_mut::<ReqInfo>().expect("ReqInfo not found");
     
         if let Some(cred) = &req_info.cred {
    @@ -48,7 +50,7 @@ pub async fn authorize_request<T>(req: &mut S3Request<T>, action: Action) -> S3R
     
             let default_claims = HashMap::new();
             let claims = cred.claims.as_ref().unwrap_or(&default_claims);
    -        let conditions = get_condition_values(&req.headers, cred, req_info.version_id.as_deref(), None);
    +        let conditions = get_condition_values(&req.headers, cred, req_info.version_id.as_deref(), None, remote_addr);
     
             if action == Action::S3Action(S3Action::DeleteObjectAction)
                 && req_info.version_id.is_some()
    @@ -109,6 +111,7 @@ pub async fn authorize_request<T>(req: &mut S3Request<T>, action: Action) -> S3R
                 &rustfs_credentials::Credentials::default(),
                 req_info.version_id.as_deref(),
                 req.region.as_deref(),
    +            remote_addr,
             );
     
             if action != Action::S3Action(S3Action::ListAllMyBucketsAction) {
    
  • rustfs/src/storage/ecfs.rs+3 1 modified
    @@ -17,6 +17,7 @@ use crate::config::workload_profiles::{
         RustFSBufferConfig, WorkloadProfile, get_global_buffer_config, is_buffer_profile_enabled,
     };
     use crate::error::ApiError;
    +use crate::server::RemoteAddr;
     use crate::storage::concurrency::{
         CachedGetObject, ConcurrencyManager, GetObjectGuard, get_concurrency_aware_buffer_size, get_concurrency_manager,
     };
    @@ -4689,7 +4690,8 @@ impl S3 for FS {
                 .await
                 .map_err(ApiError::from)?;
     
    -        let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None);
    +        let remote_addr = req.extensions.get::<RemoteAddr>().map(|a| a.0);
    +        let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None, remote_addr);
     
             let read_only = PolicySys::is_allowed(&BucketPolicyArgs {
                 bucket: &bucket,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.