-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Support for "secrecy" crate #613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The secret value will still be exposed in the connection buffer, at least until it is overwritten. I'm concerned about providing a false sense of security. |
Is there any way that a user (or sqlx) could forcibly zero out the connection buffer? As it stands currently, I'm just using a second struct and calling NB: I, personally, don't need the super strict security of zeroing memory. I'm just using the secrecy crate to mask secrets in the debug representation without having to manually implement it. Obviously use cases vary, however. |
You can pretty easily implement a newtype wrapper and manually implement #[derive(sqlx::Type)]
#[sqlx(transparent)]
pub struct SecretString(String);
impl Debug for SecretString {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad_str("(secret)")
}
} It's really a damn shame we don't have attributes to tell We could probably implement zeroing for the connection buffer, although the way it's implemented in However, keep in mind that zeroing memory won't magically make the application more secure; it's just a mitigation in case the application is already an attack target. I'm no security expert, but assuming we (or you as the user, or another crate in your dependency graph; are you auditing them all?) don't introduce an An attacker could just pull the credentials for your DB, which stay resident in memory unless you store them securely and only access them on-demand, and read all your users' juicy secrets at their leisure. This would also happen if they had arbitrary memory access. Should we also provide a mechanism for secure storage of DB credentials? Maybe. It's certainly an interesting idea. However, the attack surface for database clients and servers is so large that I don't think zeroing a single temporary buffer, which would likely be overwritten in short order anyways, would significantly hinder an attacker. It would take a comprehensive security-hardening effort that I don't think we have the time, energy or expertise to execute competently. Instead, I think we should focus on auditing for, and not introducing new, vulnerabilities rather than adding mitigations which will negatively affect performance and lure naive users into a false sense of security. Sure, we could make it opt-in which reduces the impact of the former, but then makes the latter worse as it'd be a visible feature to turn on and think "okay, everything should be good now". |
Yeah, I'm certainly no security expert — just doing what I think might help. Have you (or anyone else) considered the possibility of attributes, similar to serde? That allows renaming fields, automatically performing |
@jhpratt I was thinking of opening a PR suggesting to add something like what I use in my repos (can be extended further, probably need to implement the @abonander you think that adding native #[derive(sqlx::FromRow, Deserialize, Serialize, Debug)]
pub struct User {
pub id: Uuid,
pub username: String
#[serde(skip)]
pub password: DBHidden<String>,
} use secrecy::zeroize::DefaultIsZeroes;
use secrecy::Secret;
use sqlx::database::HasValueRef;
use sqlx::{Decode, Postgres};
use std::error::Error;
use std::fmt::{Debug, Display};
pub struct DBHidden<T>(Secret<T>)
where
T: Default + DefaultIsZeroes + Clone;
impl<T> Default for DBHidden<T>
where
T: Default + DefaultIsZeroes,
{
fn default() -> Self {
Self(Secret::new(T::default()))
}
}
impl<T> Display for DBHidden<T>
where
T: Default + DefaultIsZeroes + Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[hidden]")
}
}
impl<T> Debug for DBHidden<T>
where
T: Default + DefaultIsZeroes + Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[hidden]")
}
}
impl<T> sqlx::Type<Postgres> for DBHidden<T>
where
T: Default + DefaultIsZeroes + sqlx::Type<Postgres>,
{
fn type_info() -> sqlx::postgres::PgTypeInfo {
<T as sqlx::Type<Postgres>>::type_info()
}
}
impl<T> sqlx::Decode<'_, Postgres> for DBHidden<T>
where
for<'a> T: DefaultIsZeroes + sqlx::Type<Postgres> + sqlx::Decode<'a, Postgres>,
{
fn decode(
value: <Postgres as HasValueRef<'_>>::ValueRef,
) -> Result<Self, Box<dyn Error + 'static + Send + Sync>> {
let value = <T as Decode<Postgres>>::decode(value)?;
Ok(DBHidden::from(value))
}
}
impl<T> AsRef<Secret<T>> for DBHidden<T>
where
T: DefaultIsZeroes,
{
fn as_ref(&self) -> &Secret<T> {
&self.0
}
}
impl<T> From<T> for DBHidden<T>
where
T: DefaultIsZeroes,
{
fn from(s: T) -> Self {
Self(Secret::new(s))
}
} |
Having support for
secrecy::Secret
behind a flag would be awesome. I think the simplest way would be to implicitly useSecret::new
when decoding. When encoding, it's probably best to still require the user explicitly call.expose_secret()
The text was updated successfully, but these errors were encountered: