fix: resolve 5 critical vulnerabilities from L3 security audit
fix: resolve 5 critical vulnerabilities from L3 security audit
This commit is contained in:
@@ -1,36 +1,12 @@
|
||||
import { DEFAULT_SESSION_CLEANUP_CONFIG } from '../Defaults'
|
||||
import type { SignalKeyStoreWithTransaction } from '../Types'
|
||||
import type { SessionCleanupConfig, SessionCleanupStats, SignalKeyStoreWithTransaction } from '../Types'
|
||||
import type { ILogger } from '../Utils/logger'
|
||||
import { jidDecode } from '../WABinary'
|
||||
import type { LIDMappingStore } from './lid-mapping'
|
||||
import type { SessionActivityTracker } from './session-activity-tracker'
|
||||
|
||||
/**
|
||||
* Session cleanup statistics
|
||||
*/
|
||||
export interface SessionCleanupStats {
|
||||
totalScanned: number
|
||||
secondaryDevicesDeleted: number
|
||||
primaryDevicesDeleted: number
|
||||
lidOrphansDeleted: number
|
||||
totalDeleted: number
|
||||
durationMs: number
|
||||
errors: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Session cleanup configuration
|
||||
*/
|
||||
export interface SessionCleanupConfig {
|
||||
enabled: boolean
|
||||
intervalMs: number
|
||||
cleanupHour: number
|
||||
secondaryDeviceInactiveDays: number
|
||||
primaryDeviceInactiveDays: number
|
||||
lidOrphanHours: number
|
||||
cleanupOnStartup: boolean
|
||||
autoCleanCorrupted: boolean
|
||||
}
|
||||
// Re-export for backward compatibility
|
||||
export type { SessionCleanupConfig, SessionCleanupStats }
|
||||
|
||||
/**
|
||||
* Session metadata for cleanup decisions
|
||||
@@ -76,6 +52,7 @@ export const makeSessionCleanup = (
|
||||
let initialTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
let lastCleanupAt: number = 0
|
||||
let cleanupRunning: boolean = false
|
||||
let startupCleanupPromise: Promise<void> | null = null
|
||||
|
||||
/**
|
||||
* Get all sessions from database
|
||||
@@ -399,8 +376,10 @@ export const makeSessionCleanup = (
|
||||
// Run immediate cleanup on startup if enabled
|
||||
if (config.cleanupOnStartup) {
|
||||
logger.info('🚀 Running cleanup on startup...')
|
||||
runCleanup().catch(err => {
|
||||
startupCleanupPromise = runCleanup().catch(err => {
|
||||
logger.error({ err }, 'Cleanup on startup failed')
|
||||
}).then(() => {
|
||||
startupCleanupPromise = null // Clear after completion
|
||||
})
|
||||
}
|
||||
|
||||
@@ -414,6 +393,12 @@ export const makeSessionCleanup = (
|
||||
initialTimeout = setTimeout(async () => {
|
||||
initialTimeout = null // Clear reference after execution
|
||||
|
||||
// Wait for startup cleanup to complete (if still running)
|
||||
if (startupCleanupPromise) {
|
||||
logger.debug('Waiting for startup cleanup to complete before scheduled cleanup...')
|
||||
await startupCleanupPromise
|
||||
}
|
||||
|
||||
// Run first cleanup
|
||||
await runCleanup()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Boom } from '@hapi/boom'
|
||||
import { randomBytes } from 'crypto'
|
||||
import Long from 'long'
|
||||
import { proto } from '../../WAProto/index.js'
|
||||
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, STATUS_EXPIRY_SECONDS } from '../Defaults'
|
||||
import { DEFAULT_CACHE_TTLS, DEFAULT_SESSION_CLEANUP_CONFIG, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, STATUS_EXPIRY_SECONDS } from '../Defaults'
|
||||
import { metrics, recordMessageReceived, recordHistorySyncMessages, recordMessageRetry, recordMessageFailure } from '../Utils/prometheus-metrics.js'
|
||||
import type {
|
||||
GroupParticipant,
|
||||
@@ -76,8 +76,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
getMessage,
|
||||
shouldIgnoreJid,
|
||||
enableAutoSessionRecreation,
|
||||
enableCTWARecovery
|
||||
enableCTWARecovery,
|
||||
sessionCleanupConfig
|
||||
} = config
|
||||
// Use nullish coalescing to handle partial config properly
|
||||
const autoCleanCorrupted = sessionCleanupConfig?.autoCleanCorrupted ?? DEFAULT_SESSION_CLEANUP_CONFIG.autoCleanCorrupted
|
||||
const sock = makeMessagesSocket(config)
|
||||
const {
|
||||
ev,
|
||||
@@ -1227,7 +1230,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
category,
|
||||
author,
|
||||
decrypt
|
||||
} = decryptMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '', signalRepository, logger)
|
||||
} = decryptMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '', signalRepository, logger, autoCleanCorrupted)
|
||||
|
||||
const alt = msg.key.participantAlt || msg.key.remoteJidAlt
|
||||
// Handle LID/PN mappings with hybrid approach:
|
||||
|
||||
@@ -506,12 +506,17 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
const sessionActivityTracker = makeSessionActivityTracker(keys, logger)
|
||||
|
||||
// Session cleanup manager - removes inactive/orphaned sessions
|
||||
// Merge user config with defaults to prevent partial overrides from breaking cleanup
|
||||
const sessionCleanupConfig = {
|
||||
...DEFAULT_SESSION_CLEANUP_CONFIG,
|
||||
...(config.sessionCleanupConfig || {})
|
||||
}
|
||||
const sessionCleanup = makeSessionCleanup(
|
||||
keys,
|
||||
signalRepository.lidMapping,
|
||||
sessionActivityTracker,
|
||||
logger,
|
||||
DEFAULT_SESSION_CLEANUP_CONFIG
|
||||
sessionCleanupConfig
|
||||
)
|
||||
|
||||
let lastDateRecv: Date
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Session cleanup configuration
|
||||
*/
|
||||
export interface SessionCleanupConfig {
|
||||
enabled: boolean
|
||||
intervalMs: number
|
||||
cleanupHour: number
|
||||
secondaryDeviceInactiveDays: number
|
||||
primaryDeviceInactiveDays: number
|
||||
lidOrphanHours: number
|
||||
cleanupOnStartup: boolean
|
||||
autoCleanCorrupted: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Session cleanup statistics
|
||||
*/
|
||||
export interface SessionCleanupStats {
|
||||
totalScanned: number
|
||||
secondaryDevicesDeleted: number
|
||||
primaryDevicesDeleted: number
|
||||
lidOrphansDeleted: number
|
||||
totalDeleted: number
|
||||
durationMs: number
|
||||
errors: number
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { URL } from 'url'
|
||||
import { proto } from '../../WAProto/index.js'
|
||||
import type { ILogger } from '../Utils/logger'
|
||||
import type { CircuitBreakerOptions } from '../Utils/circuit-breaker'
|
||||
import type { SessionCleanupConfig } from './SessionCleanup'
|
||||
import type { AuthenticationState, LIDMapping, SignalAuthState, TransactionCapabilityOptions } from './Auth'
|
||||
import type { GroupMetadata } from './GroupMetadata'
|
||||
import { type MediaConnInfo, type WAMessageKey } from './Message'
|
||||
@@ -252,4 +253,7 @@ export type SocketConfig = {
|
||||
* @see https://github.com/WhiskeySockets/Baileys/pull/2294
|
||||
*/
|
||||
enableUnifiedSession?: boolean
|
||||
|
||||
/** Session cleanup configuration (optional, partial overrides merged with defaults) */
|
||||
sessionCleanupConfig?: Partial<SessionCleanupConfig>
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export * from './Product'
|
||||
export * from './Call'
|
||||
export * from './Signal'
|
||||
export * from './Newsletter'
|
||||
export * from './SessionCleanup'
|
||||
|
||||
import type { AuthenticationState } from './Auth'
|
||||
import type { SocketConfig } from './Socket'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { proto } from '../../WAProto/index.js'
|
||||
import { DEFAULT_SESSION_CLEANUP_CONFIG } from '../Defaults'
|
||||
import type { WAMessage, WAMessageKey } from '../Types'
|
||||
import type { SignalRepositoryWithLIDStore } from '../Types/Signal'
|
||||
import {
|
||||
@@ -269,7 +268,8 @@ export const decryptMessageNode = (
|
||||
meId: string,
|
||||
meLid: string,
|
||||
repository: SignalRepositoryWithLIDStore,
|
||||
logger: ILogger
|
||||
logger: ILogger,
|
||||
autoCleanCorrupted: boolean = true
|
||||
) => {
|
||||
const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid)
|
||||
return {
|
||||
@@ -407,7 +407,7 @@ export const decryptMessageNode = (
|
||||
)
|
||||
|
||||
// Automatic cleanup of corrupted session (if enabled)
|
||||
if (DEFAULT_SESSION_CLEANUP_CONFIG.autoCleanCorrupted) {
|
||||
if (autoCleanCorrupted) {
|
||||
try {
|
||||
await cleanupCorruptedSession(decryptionJid, repository, logger)
|
||||
logger.info(
|
||||
@@ -475,9 +475,21 @@ async function cleanupCorruptedSession(
|
||||
// Build list of JIDs to delete (primary + secondary devices)
|
||||
const jidsToDelete: string[] = []
|
||||
|
||||
// Determine if this is a LID or PN
|
||||
const isLID = jid.endsWith('@lid')
|
||||
const domain = isLID ? 'lid' : 's.whatsapp.net'
|
||||
// Determine domain type correctly (handle hosted JIDs)
|
||||
// JID formats:
|
||||
// - PN: user@s.whatsapp.net
|
||||
// - LID: user@lid
|
||||
// - Hosted PN: user@hosted
|
||||
// - Hosted LID: user@hosted.lid
|
||||
const isLID = jid.endsWith('@lid') || jid.endsWith('@hosted.lid')
|
||||
const isHosted = jid.includes('@hosted')
|
||||
|
||||
let domain: string
|
||||
if (isLID) {
|
||||
domain = isHosted ? 'hosted.lid' : 'lid'
|
||||
} else {
|
||||
domain = isHosted ? 'hosted' : 's.whatsapp.net'
|
||||
}
|
||||
|
||||
// Primary device (0)
|
||||
jidsToDelete.push(`${user}@${domain}`)
|
||||
|
||||
@@ -37,7 +37,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 15,
|
||||
primaryDeviceInactiveDays: 30,
|
||||
lidOrphanHours: 24
|
||||
lidOrphanHours: 24,
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
it('should delete LID orphan after 24h of inactivity', async () => {
|
||||
@@ -168,7 +170,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 15,
|
||||
primaryDeviceInactiveDays: 30,
|
||||
lidOrphanHours: 24
|
||||
lidOrphanHours: 24,
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
it('should delete secondary device (Web/Desktop) after 15 days', async () => {
|
||||
@@ -257,7 +261,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 15,
|
||||
primaryDeviceInactiveDays: 30,
|
||||
lidOrphanHours: 24
|
||||
lidOrphanHours: 24,
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
it('should delete primary device after 30 days', async () => {
|
||||
@@ -323,7 +329,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 15,
|
||||
primaryDeviceInactiveDays: 30,
|
||||
lidOrphanHours: 24
|
||||
lidOrphanHours: 24,
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
it('should handle exactly 24h for LID orphan (boundary)', async () => {
|
||||
@@ -408,7 +416,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 15,
|
||||
primaryDeviceInactiveDays: 30,
|
||||
lidOrphanHours: 24
|
||||
lidOrphanHours: 24,
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
it('should delete multiple sessions of different types', async () => {
|
||||
@@ -462,7 +472,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 15,
|
||||
primaryDeviceInactiveDays: 30,
|
||||
lidOrphanHours: 24
|
||||
lidOrphanHours: 24,
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
@@ -491,7 +503,9 @@ describe('SessionCleanup', () => {
|
||||
cleanupHour: 3,
|
||||
secondaryDeviceInactiveDays: 5, // Custom: 5 days
|
||||
primaryDeviceInactiveDays: 10, // Custom: 10 days
|
||||
lidOrphanHours: 12 // Custom: 12 hours
|
||||
lidOrphanHours: 12, // Custom: 12 hours
|
||||
cleanupOnStartup: false,
|
||||
autoCleanCorrupted: false
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
|
||||
Reference in New Issue
Block a user