Updated spam filter autolearn settings
This commit is contained in:
@@ -87,6 +87,8 @@ pub struct ClassifierConfig {
|
||||
pub min_spam_samples: u64,
|
||||
pub auto_learn_reply_ham: bool,
|
||||
pub auto_learn_card_is_ham: bool,
|
||||
pub auto_learn_spam_score: f32,
|
||||
pub auto_learn_ham_score: f32,
|
||||
pub hold_samples_for: u64,
|
||||
pub train_frequency: Option<u64>,
|
||||
}
|
||||
@@ -482,6 +484,12 @@ impl ClassifierConfig {
|
||||
auto_learn_reply_ham: config
|
||||
.property_or_default("spam-filter.trusted-reply.learn", "true")
|
||||
.unwrap_or(true),
|
||||
auto_learn_spam_score: config
|
||||
.property_or_default("spam-filter.classifier.auto-learn.spam-score", "8.0")
|
||||
.unwrap_or(8.0),
|
||||
auto_learn_ham_score: config
|
||||
.property_or_default("spam-filter.classifier.auto-learn.ham-score", "-8.0")
|
||||
.unwrap_or(-8.0),
|
||||
hold_samples_for: config
|
||||
.property_or_default::<Duration>("spam-filter.classifier.samples.hold-for", "180d")
|
||||
.unwrap_or(Duration::from_secs(180 * 24 * 60 * 60))
|
||||
|
||||
@@ -31,7 +31,9 @@ use jmap_proto::{
|
||||
use jmap_tools::{JsonPointerItem, Key, Map, Value};
|
||||
use std::future::Future;
|
||||
use store::{
|
||||
ValueKey, roaring::RoaringBitmap, write::{AlignedBytes, Archive, BatchBuilder, assert::AssertValue}
|
||||
ValueKey,
|
||||
roaring::RoaringBitmap,
|
||||
write::{AlignedBytes, Archive, BatchBuilder, assert::AssertValue},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
@@ -526,12 +528,12 @@ impl MailboxSet for Server {
|
||||
|
||||
// Role of internal folders cannot be modified
|
||||
if update.as_ref().is_some_and(|(document_id, _)| {
|
||||
*document_id == INBOX_ID || *document_id == TRASH_ID
|
||||
*document_id == INBOX_ID || *document_id == TRASH_ID || *document_id == JUNK_ID
|
||||
}) {
|
||||
return Ok(Err(SetError::invalid_properties()
|
||||
.with_property(MailboxProperty::Role)
|
||||
.with_description(
|
||||
"You are not allowed to change the role of Inbox or Trash folders.",
|
||||
"You are not allowed to change the role of Inbox, Junk or Trash folders.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ impl<T: SessionStream> Session<T> {
|
||||
}
|
||||
|
||||
// Run SPAM filter
|
||||
let mut train_as_spam = false;
|
||||
let mut train_spam = None;
|
||||
if self.server.core.spam.enabled
|
||||
&& self
|
||||
.server
|
||||
@@ -446,7 +446,7 @@ impl<T: SessionStream> Session<T> {
|
||||
SpamFilterAction::Allow(score) => {
|
||||
// Add headers
|
||||
headers.extend_from_slice(score.headers.as_bytes());
|
||||
train_as_spam = score.spam_trap;
|
||||
train_spam = score.train_spam;
|
||||
|
||||
// Add scores for local recipients
|
||||
for (is_spam, recipient) in
|
||||
@@ -685,7 +685,7 @@ impl<T: SessionStream> Session<T> {
|
||||
{
|
||||
MessageSource::Unauthenticated {
|
||||
dmarc_pass: dmarc_pass || message.message.return_path.starts_with("dmarc-"),
|
||||
train_as_spam,
|
||||
train_spam,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,7 +693,7 @@ impl<T: SessionStream> Session<T> {
|
||||
{
|
||||
MessageSource::Unauthenticated {
|
||||
dmarc_pass,
|
||||
train_as_spam,
|
||||
train_spam,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -45,7 +45,7 @@ pub enum MessageSource {
|
||||
Authenticated,
|
||||
Unauthenticated {
|
||||
dmarc_pass: bool,
|
||||
train_as_spam: bool,
|
||||
train_spam: Option<bool>,
|
||||
},
|
||||
Dsn,
|
||||
Report,
|
||||
|
||||
@@ -306,30 +306,30 @@ impl MessageWrapper {
|
||||
MessageSource::Authenticated => (
|
||||
FROM_AUTHENTICATED,
|
||||
trc::QueueEvent::QueueMessageAuthenticated,
|
||||
false,
|
||||
None,
|
||||
),
|
||||
MessageSource::Unauthenticated {
|
||||
dmarc_pass: true,
|
||||
train_as_spam,
|
||||
train_spam,
|
||||
} => (
|
||||
FROM_UNAUTHENTICATED_DMARC,
|
||||
trc::QueueEvent::QueueMessage,
|
||||
train_as_spam,
|
||||
train_spam,
|
||||
),
|
||||
MessageSource::Unauthenticated {
|
||||
dmarc_pass: false,
|
||||
train_as_spam,
|
||||
train_spam,
|
||||
} => (
|
||||
FROM_UNAUTHENTICATED,
|
||||
trc::QueueEvent::QueueMessage,
|
||||
train_as_spam,
|
||||
train_spam,
|
||||
),
|
||||
MessageSource::Dsn => (FROM_DSN, trc::QueueEvent::QueueDsn, false),
|
||||
MessageSource::Report => (FROM_REPORT, trc::QueueEvent::QueueReport, false),
|
||||
MessageSource::Dsn => (FROM_DSN, trc::QueueEvent::QueueDsn, None),
|
||||
MessageSource::Report => (FROM_REPORT, trc::QueueEvent::QueueReport, None),
|
||||
MessageSource::Autogenerated => (
|
||||
FROM_AUTOGENERATED,
|
||||
trc::QueueEvent::QueueAutogenerated,
|
||||
false,
|
||||
None,
|
||||
),
|
||||
};
|
||||
self.message.flags |= flags;
|
||||
@@ -439,7 +439,9 @@ impl MessageWrapper {
|
||||
);
|
||||
}
|
||||
|
||||
if train_spam && let Some(config) = &server.core.spam.classifier {
|
||||
if let Some(is_spam) = train_spam
|
||||
&& let Some(config) = &server.core.spam.classifier
|
||||
{
|
||||
let hold_period = now + config.hold_samples_for;
|
||||
|
||||
batch
|
||||
@@ -455,12 +457,12 @@ impl MessageWrapper {
|
||||
hash: self.message.blob_hash.clone(),
|
||||
until: hold_period,
|
||||
},
|
||||
vec![1, 1],
|
||||
vec![u8::from(is_spam), 1],
|
||||
);
|
||||
|
||||
trc::event!(
|
||||
Spam(SpamEvent::TrainSampleAdded),
|
||||
Details = "spam",
|
||||
Details = if is_spam { "spam" } else { "ham" },
|
||||
Expires = trc::Value::Timestamp(hold_period),
|
||||
SpanId = self.span_id,
|
||||
);
|
||||
|
||||
@@ -36,7 +36,6 @@ impl SpamFilterAnalyzeClassify for Server {
|
||||
match store.key_exists(addr.address.as_str()).await {
|
||||
Ok(true) => {
|
||||
ctx.result.add_tag("SPAM_TRAP");
|
||||
ctx.result.spam_trap = true;
|
||||
return true;
|
||||
}
|
||||
Ok(false) => (),
|
||||
|
||||
@@ -44,7 +44,7 @@ pub trait SpamFilterAnalyzeScore: Sync + Send {
|
||||
pub struct SpamFilterScore {
|
||||
pub results: Vec<bool>,
|
||||
pub headers: String,
|
||||
pub spam_trap: bool,
|
||||
pub train_spam: Option<bool>,
|
||||
pub score: f32,
|
||||
}
|
||||
|
||||
@@ -163,10 +163,24 @@ impl SpamFilterAnalyzeScore for Server {
|
||||
);
|
||||
}
|
||||
|
||||
// Autolearn
|
||||
let mut train_spam = None;
|
||||
let config = self.core.spam.classifier.as_ref().unwrap();
|
||||
if config.auto_learn_spam_score > 0.0 && final_score >= config.auto_learn_spam_score {
|
||||
if !ctx.result.has_tag("PROB_SPAM_HIGH") {
|
||||
train_spam = Some(true);
|
||||
}
|
||||
} else if config.auto_learn_ham_score < 0.0
|
||||
&& final_score <= config.auto_learn_ham_score
|
||||
&& !ctx.result.has_tag("PROB_HAM_HIGH")
|
||||
{
|
||||
train_spam = Some(false);
|
||||
}
|
||||
|
||||
SpamFilterAction::Allow(SpamFilterScore {
|
||||
results: user_results,
|
||||
headers,
|
||||
spam_trap: ctx.result.spam_trap,
|
||||
train_spam,
|
||||
score: final_score,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ pub struct SpamFilterResult {
|
||||
pub rbl_url_checks: usize,
|
||||
pub rbl_email_checks: usize,
|
||||
pub llm_result: Option<(String, String)>,
|
||||
pub spam_trap: bool,
|
||||
}
|
||||
|
||||
pub struct SpamFilterContext<'x> {
|
||||
|
||||
Reference in New Issue
Block a user