High severityNVD Advisory· Published Jul 21, 2022· Updated Apr 23, 2025
Slack Morphism for Rust before 0.41.0 can accidentally leak Slack OAuth client information in application debug logs
CVE-2022-31162
Description
Slack Morphism is an async client library for Rust. Prior to 0.41.0, it was possible for Slack OAuth client information to leak in application debug logs. Stricter and more secure debug formatting was introduced in v0.41.0 for OAuth secret types to reduce the possibility of printing sensitive information in application logs. As a workaround, do not print/output requests and responses for OAuth and client configurations in logs.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
slack-morphismcrates.io | < 0.41.0 | 0.41.0 |
Affected products
1- Range: < 0.41.0
Patches
14923fb7d458eImproved OAuth/signature verifier data types and error handling (#133)
10 files changed · +142 −58
docs/src/events-api.md+33 −17 modified@@ -114,23 +114,23 @@ async fn create_slack_events_listener_server() -> Result<(), Box<dyn std::error: // You can additionally configure HTTP route paths using theses configs, // but for simplicity we will skip that part here and configure only required parameters. let oauth_listener_config = Arc::new(SlackOAuthListenerConfig::new( - std::env::var("SLACK_CLIENT_ID")?, - std::env::var("SLACK_CLIENT_SECRET")?, - std::env::var("SLACK_BOT_SCOPE")?, - std::env::var("SLACK_REDIRECT_HOST")?, + config_env_var("SLACK_CLIENT_ID")?.into(), + config_env_var("SLACK_CLIENT_SECRET")?.into(), + config_env_var("SLACK_BOT_SCOPE")?, + config_env_var("SLACK_REDIRECT_HOST")?, )); - let push_events_config = Arc::new(SlackPushEventsListenerConfig::new(std::env::var( - "SLACK_SIGNING_SECRET", - )?)); + let push_events_config = Arc::new(SlackPushEventsListenerConfig::new( + config_env_var("SLACK_SIGNING_SECRET")?.into(), + )); let interactions_events_config = Arc::new(SlackInteractionEventsListenerConfig::new( - std::env::var("SLACK_SIGNING_SECRET")?, + config_env_var("SLACK_SIGNING_SECRET")?.into(), )); - let command_events_config = Arc::new(SlackCommandEventsListenerConfig::new(std::env::var( - "SLACK_SIGNING_SECRET", - )?)); + let command_events_config = Arc::new(SlackCommandEventsListenerConfig::new( + config_env_var("SLACK_SIGNING_SECRET")?.into(), + )); // Creating a shared listener environment with an ability to share client and user state let listener_environment = Arc::new( @@ -189,11 +189,27 @@ async fn create_slack_events_listener_server() -> Result<(), Box<dyn std::error: } ``` - Also the library provides Slack events signature verifier (`SlackEventSignatureVerifier`), - which is already integrated in the routes implementation for you and you don't need to use - it directly. All you need is provide your client id and secret configuration - to route implementation. - +## Testing with ngrok +For development/testing purposes you can use [ngrok](https://ngrok.com/): +``` +ngrok http 8080 +``` +and copy the URL it gives for you to the example parameters for `SLACK_REDIRECT_HOST`. + +Example testing with ngrok: +``` +SLACK_CLIENT_ID=<your-client-id> \ +SLACK_CLIENT_SECRET=<your-client-secret> \ +SLACK_BOT_SCOPE=app_mentions:read,incoming-webhook \ +SLACK_REDIRECT_HOST=https://<your-ngrok-url>.ngrok.io \ +SLACK_SIGNING_SECRET=<your-signing-secret> \ +cargo run --example events_api_server +``` + +## Slack Signature Verifier + The library provides Slack events signature verifier (`SlackEventSignatureVerifier`), + which is already integrated in the OAuth routes implementation for you, and you don't need to use it directly. + All you need is provide your client id and secret configuration to route implementation. Look at the [complete example here](https://github.com/abdolence/slack-morphism-rust/tree/master/src/hyper/examples/events_api_server.rs). - + In case you're embedding the library into your own Web/routes-framework, you can use it separately.
README.md+17 −0 modified@@ -33,6 +33,23 @@ Routes for this example are available on http://<your-host>:8080: - /interaction - for Slack Interaction Events - /command - for Slack Command Events +### Testing with ngrok +For development/testing purposes you can use [ngrok](https://ngrok.com/): +``` +ngrok http 8080 +``` +and copy the URL it gives for you to the example parameters for `SLACK_REDIRECT_HOST`. + +Example testing with ngrok: +``` +SLACK_CLIENT_ID=<your-client-id> \ +SLACK_CLIENT_SECRET=<your-client-secret> \ +SLACK_BOT_SCOPE=app_mentions:read,incoming-webhook \ +SLACK_REDIRECT_HOST=https://<your-ngrok-url>.ngrok.io \ +SLACK_SIGNING_SECRET=<your-signing-secret> \ +cargo run --example events_api_server +``` + ## Licence Apache Software License (ASL)
src/client/src/api/oauth.rs+23 −6 modified@@ -3,8 +3,10 @@ //! use rsb_derive::Builder; +use rvstruct::*; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::fmt; use crate::client::*; use crate::token::*; @@ -25,8 +27,14 @@ where let full_uri: Url = SlackClientHttpApiUri::create_url_with_params( &SlackClientHttpApiUri::create_method_uri_path("oauth.v2.access"), &vec![ - ("code", Some(&req.code)), - ("redirect_uri", req.redirect_uri.as_ref()), + ("code", Some(req.code.value())), + ( + "redirect_uri", + req.redirect_uri + .as_ref() + .map(|url| url.as_str().to_string()) + .as_ref(), + ), ], ); @@ -42,14 +50,14 @@ where pub struct SlackOAuthV2AccessTokenRequest { pub client_id: SlackClientId, pub client_secret: SlackClientSecret, - pub code: String, - pub redirect_uri: Option<String>, + pub code: SlackOAuthCode, + pub redirect_uri: Option<Url>, } #[skip_serializing_none] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)] pub struct SlackOAuthV2AccessTokenResponse { - pub access_token: String, + pub access_token: SlackApiTokenValue, pub token_type: SlackApiTokenType, pub scope: SlackApiTokenScope, pub bot_user_id: Option<SlackUserId>, @@ -64,7 +72,7 @@ pub struct SlackOAuthV2AccessTokenResponse { pub struct SlackOAuthV2AuthedUser { pub id: SlackUserId, pub scope: Option<SlackApiTokenScope>, - pub access_token: Option<String>, + pub access_token: Option<SlackApiTokenValue>, pub token_type: Option<SlackApiTokenType>, } @@ -76,3 +84,12 @@ pub struct SlackOAuthIncomingWebHook { pub configuration_url: Url, pub url: Url, } + +#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] +pub struct SlackOAuthCode(pub String); + +impl fmt::Debug for SlackOAuthCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "SlackOAuthCode(len:{})", self.value().len()) + } +}
src/client/src/errors.rs+9 −0 modified@@ -3,6 +3,7 @@ use std::error::Error; use std::fmt::Display; use std::fmt::Formatter; use std::time::Duration; +use url::ParseError; #[derive(Debug)] pub enum SlackClientError { @@ -190,3 +191,11 @@ impl Display for SlackRateLimitError { } impl Error for SlackRateLimitError {} + +impl From<url::ParseError> for SlackClientError { + fn from(url_parse_error: ParseError) -> Self { + SlackClientError::HttpProtocolError( + SlackClientHttpProtocolError::new().with_cause(Box::new(url_parse_error)), + ) + } +}
src/client/src/listener.rs+16 −10 modified@@ -1,12 +1,14 @@ -use crate::{SlackClient, SlackClientHttpConnector}; +use crate::{ClientResult, SlackClient, SlackClientHttpConnector}; use futures::executor::block_on; use futures::FutureExt; use rsb_derive::Builder; +use slack_morphism_models::{SlackClientId, SlackClientSecret, SlackSigningSecret}; use std::any::{Any, TypeId}; use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; use tracing::*; +use url::Url; type UserStatesMap = HashMap<TypeId, Box<dyn Any + Send + Sync + 'static>>; @@ -100,7 +102,7 @@ pub type ErrorHandler<SCHC> = fn( #[derive(Debug, PartialEq, Clone, Builder)] pub struct SlackCommandEventsListenerConfig { - pub events_signing_secret: String, + pub events_signing_secret: SlackSigningSecret, #[default = "SlackCommandEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"] pub events_path: String, } @@ -111,7 +113,7 @@ impl SlackCommandEventsListenerConfig { #[derive(Debug, PartialEq, Clone, Builder)] pub struct SlackPushEventsListenerConfig { - pub events_signing_secret: String, + pub events_signing_secret: SlackSigningSecret, #[default = "SlackPushEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"] pub events_path: String, } @@ -122,7 +124,7 @@ impl SlackPushEventsListenerConfig { #[derive(Debug, PartialEq, Clone, Builder)] pub struct SlackInteractionEventsListenerConfig { - pub events_signing_secret: String, + pub events_signing_secret: SlackSigningSecret, #[default = "SlackInteractionEventsListenerConfig::DEFAULT_EVENTS_URL_VALUE.into()"] pub events_path: String, } @@ -133,8 +135,8 @@ impl SlackInteractionEventsListenerConfig { #[derive(Debug, PartialEq, Clone, Builder)] pub struct SlackOAuthListenerConfig { - pub client_id: String, - pub client_secret: String, + pub client_id: SlackClientId, + pub client_secret: SlackClientSecret, pub bot_scope: String, pub redirect_callback_host: String, #[default = "SlackOAuthListenerConfig::DEFAULT_INSTALL_PATH_VALUE.into()"] @@ -158,11 +160,15 @@ impl SlackOAuthListenerConfig { pub const OAUTH_AUTHORIZE_URL_VALUE: &'static str = "https://slack.com/oauth/v2/authorize"; - pub fn to_redirect_url(&self) -> String { - format!( - "{}{}", - &self.redirect_callback_host, &self.redirect_callback_path + pub fn to_redirect_url(&self) -> ClientResult<Url> { + Url::parse( + format!( + "{}{}", + &self.redirect_callback_host, &self.redirect_callback_path + ) + .as_str(), ) + .map_err(|e| e.into()) } }
src/client/src/signature_verifier.rs+13 −7 modified@@ -1,9 +1,11 @@ use ring::hmac; use rsb_derive::Builder; +use rvstruct::*; +use slack_morphism_models::SlackSigningSecret; use std::error::Error; use std::fmt::{Display, Formatter}; -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct SlackEventSignatureVerifier { secret_len: usize, key: hmac::Key, @@ -13,11 +15,11 @@ impl SlackEventSignatureVerifier { pub const SLACK_SIGNED_HASH_HEADER: &'static str = "x-slack-signature"; pub const SLACK_SIGNED_TIMESTAMP: &'static str = "x-slack-request-timestamp"; - pub fn new(secret: &str) -> Self { - let secret_bytes = secret.as_bytes(); + pub fn new(secret: &SlackSigningSecret) -> Self { + let secret_bytes = secret.value().as_bytes(); SlackEventSignatureVerifier { secret_len: secret_bytes.len(), - key: hmac::Key::new(hmac::HMAC_SHA256, secret.as_bytes()), + key: hmac::Key::new(hmac::HMAC_SHA256, secret_bytes), } } @@ -143,7 +145,7 @@ fn check_signature_success() { ring::rand::generate(&rng).unwrap().expose(); let key_str: String = hex::encode(key_value); - let verifier = SlackEventSignatureVerifier::new(&key_str); + let verifier = SlackEventSignatureVerifier::new(&key_str.to_string().into()); const TEST_BODY: &'static str = "test-body"; const TEST_TS: &'static str = "test-ts"; @@ -167,7 +169,7 @@ fn test_precoded_data() { const TEST_BODY: &'static str = "test-body"; const TEST_TS: &'static str = "test-ts"; - let verifier = SlackEventSignatureVerifier::new(TEST_SECRET); + let verifier = SlackEventSignatureVerifier::new(&TEST_SECRET.to_string().into()); match verifier.verify(TEST_HASH, TEST_BODY, TEST_TS) { Ok(_) => {} @@ -179,7 +181,11 @@ fn test_precoded_data() { #[test] fn check_empty_secret_error_test() { - match SlackEventSignatureVerifier::new("").verify("test-hash", "test-body", "test-ts") { + match SlackEventSignatureVerifier::new(&"".to_string().into()).verify( + "test-hash", + "test-body", + "test-ts", + ) { Err(SlackEventSignatureVerifierError::CryptoInitError(ref err)) => { assert!(!err.message.is_empty()) }
src/client/src/token.rs+1 −4 modified@@ -3,10 +3,7 @@ use rvstruct::ValueStruct; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use slack_morphism_models::SlackTeamId; - -// Re-exports for backward compatibility -pub use slack_morphism_models::SlackApiTokenScope; +use slack_morphism_models::{SlackApiTokenScope, SlackTeamId}; #[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] pub struct SlackApiTokenValue(pub String);
src/hyper/examples/events_api_server.rs+9 −9 modified@@ -97,23 +97,23 @@ async fn test_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { } let oauth_listener_config = Arc::new(SlackOAuthListenerConfig::new( - config_env_var("SLACK_CLIENT_ID")?, - config_env_var("SLACK_CLIENT_SECRET")?, + config_env_var("SLACK_CLIENT_ID")?.into(), + config_env_var("SLACK_CLIENT_SECRET")?.into(), config_env_var("SLACK_BOT_SCOPE")?, config_env_var("SLACK_REDIRECT_HOST")?, )); - let push_events_config = Arc::new(SlackPushEventsListenerConfig::new(config_env_var( - "SLACK_SIGNING_SECRET", - )?)); + let push_events_config = Arc::new(SlackPushEventsListenerConfig::new( + config_env_var("SLACK_SIGNING_SECRET")?.into(), + )); let interactions_events_config = Arc::new(SlackInteractionEventsListenerConfig::new( - config_env_var("SLACK_SIGNING_SECRET")?, + config_env_var("SLACK_SIGNING_SECRET")?.into(), )); - let command_events_config = Arc::new(SlackCommandEventsListenerConfig::new(config_env_var( - "SLACK_SIGNING_SECRET", - )?)); + let command_events_config = Arc::new(SlackCommandEventsListenerConfig::new( + config_env_var("SLACK_SIGNING_SECRET")?.into(), + )); let listener_environment = Arc::new( SlackClientEventsListenerEnvironment::new(client.clone())
src/hyper/src/listener/oauth.rs+7 −3 modified@@ -10,6 +10,7 @@ use futures::future::{BoxFuture, FutureExt}; use hyper::body::*; use hyper::client::connect::Connect; use hyper::{Method, Request, Response}; +use rvstruct::*; use std::future::Future; use std::sync::Arc; use tracing::*; @@ -22,9 +23,12 @@ impl<H: 'static + Send + Sync + Connect + Clone> SlackClientEventsHyperListener< let full_uri = SlackClientHttpApiUri::create_url_with_params( SlackOAuthListenerConfig::OAUTH_AUTHORIZE_URL_VALUE, &vec![ - ("client_id", Some(&config.client_id)), + ("client_id", Some(config.client_id.value())), ("scope", Some(&config.bot_scope)), - ("redirect_uri", Some(&config.to_redirect_url())), + ( + "redirect_uri", + Some(config.to_redirect_url()?.as_str().to_string()).as_ref(), + ), ], ); debug!("Redirecting to Slack OAuth authorize: {}", &full_uri); @@ -55,7 +59,7 @@ impl<H: 'static + Send + Sync + Connect + Clone> SlackClientEventsHyperListener< client_secret: config.client_secret.clone().into(), code: code.into(), }) - .with_redirect_uri(config.to_redirect_url()), + .with_redirect_uri(config.to_redirect_url()?), ) .await;
src/models/src/common/mod.rs+14 −2 modified@@ -109,9 +109,15 @@ pub struct SlackCommandId(pub String); #[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] pub struct SlackClientId(pub String); -#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] +#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] pub struct SlackClientSecret(pub String); +impl fmt::Debug for SlackClientSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SlackClientSecret(len:{})", self.value().len()) + } +} + #[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] pub struct SlackApiTokenScope(pub String); @@ -124,9 +130,15 @@ impl fmt::Debug for SlackVerificationToken { } } -#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] +#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] pub struct SlackSigningSecret(pub String); +impl fmt::Debug for SlackSigningSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SlackSigningSecret(len:{})", self.value().len()) + } +} + #[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, ValueStruct)] pub struct EmailAddress(pub String);
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
7- github.com/advisories/GHSA-99j7-mhfh-w84pghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-31162ghsaADVISORY
- github.com/abdolence/slack-morphism-rust/commit/4923fb7d458ed28c0302244c54cb4df0acee7ee6ghsaWEB
- github.com/abdolence/slack-morphism-rust/pull/133ghsaWEB
- github.com/abdolence/slack-morphism-rust/releases/tag/v0.41.0ghsax_refsource_MISCWEB
- github.com/abdolence/slack-morphism-rust/security/advisories/GHSA-99j7-mhfh-w84pghsax_refsource_CONFIRMWEB
- rustsec.org/advisories/RUSTSEC-2022-0086.htmlghsaWEB
News mentions
0No linked articles in our index yet.