export interface EmailPollerOptions { host: string; port: number; secure: boolean; user: string; pass: string; /** Sender allowlist (regex/substring patterns). Empty array = allow NOBODY. */ allowedSenders: string[]; /** Verify TLS cert. Default true. */ rejectUnauthorized?: boolean; /** * Require the receiving MTA's Authentication-Results header to show * dkim/spf=pass aligned to the sender domain before ingesting a message. * Default true — the From header is trivially spoofable and inbound mail runs * autonomous full-tool tasks. Set false only on a trusted internal relay. */ requireAuth?: boolean; /** * Optional authserv-id of the trusted receiving MTA (the token that opens its * Authentication-Results header, e.g. "mx.google.com"). When set, only an AR * header stamped by this id is trusted — hardening against a forged topmost * header on an MTA that adds none of its own. */ authServId?: string; } /** * Decide whether a message is authenticated as genuinely from `senderDomain`. * * The From header is forgeable, so authorization must rest on the receiving * MTA's DKIM/SPF verdict. Only the TOPMOST Authentication-Results header is * trusted: a conformant MTA strips forged copies bearing its own authserv-id * and prepends its own, so the first AR line (as it appears in the raw header * block) is the one our server added; lines below it may be attacker-supplied * and are ignored. Requires dkim=pass with header.d aligned to the sender * domain, or spf=pass with smtp.mailfrom aligned. Returns false when no AR * header is present (fail closed). * * Exported for unit testing. */ export declare function emailPassesAuth(arLines: string[], senderDomain: string, authServId?: string): boolean; export declare class EmailTaskPoller { private readonly opts; private client; private connecting; private polling; private readonly allowed; /** * Snapshot of UNSEEN UIDs that existed when the poller first connected. * Those messages are ignored — they were already in the inbox before the * loop started and aren't "new tasks". Cleared after the first poll. */ private skipUids; /** Wall-clock time of the last successful connect — used to force a periodic * reconnect so a long-running session doesn't sit on a half-dead socket the * IMAP server has silently dropped. */ private connectedAt; /** Force a fresh IMAP connection every 15 minutes regardless of state. */ private static readonly MAX_CONN_AGE_MS; private readonly requireAuth; private readonly authServId?; constructor(opts: EmailPollerOptions); /** * Connect once. Any UNSEEN message in the inbox — both pre-existing and * future arrivals — becomes a task. The poller marks each one \Seen after * processing so a restart never re-ingests it. */ initialize(): Promise; pollAndAppend(todoPath: string, workspaceRoot?: string): Promise; dispose(): Promise; private _ensureConnected; private _senderAllowed; /** * True if the receiving MTA authenticated this message as genuinely from the * sender's domain (DKIM or SPF pass, topmost Authentication-Results only). */ private _isAuthenticated; private _buildTaskFromMessage; }