BREAKING CHANGE(rust-bridge): make Rust the primary security backend, remove all TS fallbacks
Some checks failed
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Type Check & Lint (push) Failing after 6s
CI / Build All Platforms (push) Failing after 4s

Phase 3 of the Rust migration: the Rust security bridge is now mandatory
and all TypeScript security fallback implementations have been removed.

- UnifiedEmailServer.start() throws if Rust bridge fails to start
- SpfVerifier gutted to thin wrapper (parseSpfRecord stays in TS)
- DKIMVerifier gutted to thin wrapper delegating to bridge.verifyDkim()
- IPReputationChecker delegates to bridge.checkIpReputation(), keeps LRU cache
- DmarcVerifier keeps alignment logic (works with pre-computed results)
- DKIM signing via bridge.signDkim() in all 4 locations
- Removed mailauth and ip packages from plugins.ts (~1,200 lines deleted)
This commit is contained in:
2026-02-10 20:30:43 +00:00
parent ffe294643c
commit b82468ab1e
24 changed files with 457 additions and 2695 deletions

View File

@@ -366,13 +366,12 @@ export class UnifiedEmailServer extends EventEmitter {
await this.deliverySystem.start();
logger.log('info', 'Email delivery system started');
// Start Rust security bridge (non-blocking — server works without it)
// Start Rust security bridge — required for all security operations
const bridgeOk = await this.rustBridge.start();
if (bridgeOk) {
logger.log('info', 'Rust security bridge started — using Rust for DKIM/SPF/DMARC verification');
} else {
logger.log('warn', 'Rust security bridge unavailable — falling back to TypeScript security verification');
if (!bridgeOk) {
throw new Error('Rust security bridge failed to start. The mailer-bin binary is required. Run "pnpm build" to compile it.');
}
logger.log('info', 'Rust security bridge started — Rust is the primary security backend');
// Set up DKIM for all domains
await this.setupDkimForDomains();
@@ -430,39 +429,36 @@ export class UnifiedEmailServer extends EventEmitter {
verifyDmarc: true
}
},
// Security verification delegated to the Rust bridge when available
// Security verification delegated to the Rust bridge
dkimVerifier: {
verify: async (rawMessage: string) => {
if (this.rustBridge.running) {
try {
const results = await this.rustBridge.verifyDkim(rawMessage);
const first = results[0];
return { isValid: first?.is_valid ?? false, domain: first?.domain ?? '' };
} catch (err) {
logger.log('warn', `Rust DKIM verification failed, accepting: ${(err as Error).message}`);
return { isValid: true, domain: '' };
}
try {
const results = await this.rustBridge.verifyDkim(rawMessage);
const first = results[0];
return { isValid: first?.is_valid ?? false, domain: first?.domain ?? '' };
} catch (err) {
logger.log('warn', `Rust DKIM verification failed: ${(err as Error).message}`);
return { isValid: false, domain: '' };
}
return { isValid: true, domain: '' }; // No bridge — accept
}
},
spfVerifier: {
verifyAndApply: async (session: any) => {
if (this.rustBridge.running && session?.remoteAddress && session.remoteAddress !== '127.0.0.1') {
try {
const result = await this.rustBridge.checkSpf({
ip: session.remoteAddress,
heloDomain: session.clientHostname || '',
hostname: this.options.hostname,
mailFrom: session.envelope?.mailFrom?.address || session.mailFrom || '',
});
return result.result === 'pass' || result.result === 'none' || result.result === 'neutral';
} catch (err) {
logger.log('warn', `Rust SPF check failed, accepting: ${(err as Error).message}`);
return true;
}
if (!session?.remoteAddress || session.remoteAddress === '127.0.0.1') {
return true; // localhost — skip SPF
}
try {
const result = await this.rustBridge.checkSpf({
ip: session.remoteAddress,
heloDomain: session.clientHostname || '',
hostname: this.options.hostname,
mailFrom: session.envelope?.mailFrom?.address || session.mailFrom || '',
});
return result.result === 'pass' || result.result === 'none' || result.result === 'neutral';
} catch (err) {
logger.log('warn', `Rust SPF check failed: ${(err as Error).message}`);
return true; // Accept on error to avoid blocking mail
}
return true; // No bridge or localhost — accept
}
},
dmarcVerifier: {
@@ -637,10 +633,6 @@ export class UnifiedEmailServer extends EventEmitter {
* Falls back gracefully if the bridge is not running.
*/
private async verifyInboundSecurity(email: Email, session: IExtendedSmtpSession): Promise<void> {
if (!this.rustBridge.running) {
return; // Bridge not available — skip verification
}
try {
const rawMessage = session.emailData || email.toRFC822String();
const result = await this.rustBridge.verifyEmail({
@@ -942,52 +934,10 @@ export class UnifiedEmailServer extends EventEmitter {
// Apply DKIM signing if enabled
if (options.dkimSign && options.dkimOptions) {
// Sign the email with DKIM
logger.log('info', `Signing email with DKIM for domain ${options.dkimOptions.domainName}`);
try {
// Ensure DKIM keys exist for the domain
await this.dkimCreator.handleDKIMKeysForDomain(options.dkimOptions.domainName);
// Convert Email to raw format for signing
const rawEmail = email.toRFC822String();
// Create headers object
const headers = {};
for (const [key, value] of Object.entries(email.headers)) {
headers[key] = value;
}
// Sign the email
const dkimDomain = options.dkimOptions.domainName;
const dkimSelector = options.dkimOptions.keySelector || 'mta';
const dkimPrivateKey = (await this.dkimCreator.readDKIMKeys(dkimDomain)).privateKey;
const signResult = await plugins.dkimSign(rawEmail, {
signingDomain: dkimDomain,
selector: dkimSelector,
privateKey: dkimPrivateKey,
canonicalization: 'relaxed/relaxed',
algorithm: 'rsa-sha256',
signTime: new Date(),
signatureData: [
{
signingDomain: dkimDomain,
selector: dkimSelector,
privateKey: dkimPrivateKey,
algorithm: 'rsa-sha256',
canonicalization: 'relaxed/relaxed'
}
]
});
// Add the DKIM-Signature header to the email
if (signResult.signatures) {
email.addHeader('DKIM-Signature', signResult.signatures);
logger.log('info', `Successfully added DKIM signature for ${options.dkimOptions.domainName}`);
}
} catch (error) {
logger.log('error', `Failed to sign email with DKIM: ${error.message}`);
}
const dkimDomain = options.dkimOptions.domainName;
const dkimSelector = options.dkimOptions.keySelector || 'mta';
logger.log('info', `Signing email with DKIM for domain ${dkimDomain}`);
await this.handleDkimSigning(email, dkimDomain, dkimSelector);
}
}
@@ -1555,35 +1505,23 @@ export class UnifiedEmailServer extends EventEmitter {
try {
// Ensure we have DKIM keys for this domain
await this.dkimCreator.handleDKIMKeysForDomain(domain);
// Get the private key
const { privateKey } = await this.dkimCreator.readDKIMKeys(domain);
// Convert Email to raw format for signing
const rawEmail = email.toRFC822String();
// Sign the email
const signResult = await plugins.dkimSign(rawEmail, {
signingDomain: domain,
selector: selector,
privateKey: privateKey,
canonicalization: 'relaxed/relaxed',
algorithm: 'rsa-sha256',
signTime: new Date(),
signatureData: [
{
signingDomain: domain,
selector: selector,
privateKey: privateKey,
algorithm: 'rsa-sha256',
canonicalization: 'relaxed/relaxed'
}
]
// Sign the email via Rust bridge
const signResult = await this.rustBridge.signDkim({
rawMessage: rawEmail,
domain,
selector,
privateKey,
});
// Add the DKIM-Signature header to the email
if (signResult.signatures) {
email.addHeader('DKIM-Signature', signResult.signatures);
if (signResult.header) {
email.addHeader('DKIM-Signature', signResult.header);
logger.log('info', `Successfully added DKIM signature for ${domain}`);
}
} catch (error) {