@agenticmail/core 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +277 -92
- package/dist/index.d.cts +249 -1
- package/dist/index.d.ts +249 -1
- package/dist/index.js +266 -91
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1583,6 +1583,254 @@ declare function flushTelemetry(): void;
|
|
|
1583
1583
|
declare function debug(tag: string, message: string): void;
|
|
1584
1584
|
declare function debugWarn(tag: string, message: string): void;
|
|
1585
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* Path-traversal safe filesystem joiner.
|
|
1588
|
+
*
|
|
1589
|
+
* # Why this exists
|
|
1590
|
+
*
|
|
1591
|
+
* Every host-integration installer writes files into a directory
|
|
1592
|
+
* derived from operator config / env vars (`CODEX_HOME`,
|
|
1593
|
+
* `CLAUDE_CODE_AGENTS_DIR`, etc) AND builds filenames from values
|
|
1594
|
+
* returned by the AgenticMail API (account names, agent metadata).
|
|
1595
|
+
* Both inputs cross trust boundaries:
|
|
1596
|
+
*
|
|
1597
|
+
* - The operator's env vars are mostly trusted — but the operator
|
|
1598
|
+
* could fat-finger a relative path that resolves outside the
|
|
1599
|
+
* intended dir, or paste a path containing `..` segments.
|
|
1600
|
+
* - The AgenticMail API responses cross the master-key boundary.
|
|
1601
|
+
* A compromised MCP session or stolen master key could create an
|
|
1602
|
+
* account whose `name` contains `../../etc/something` to escape
|
|
1603
|
+
* the agents directory at install time.
|
|
1604
|
+
*
|
|
1605
|
+
* The existing `sanitizeSubagentName` helpers filter most malicious
|
|
1606
|
+
* names, but defence-in-depth dictates a second boundary check at
|
|
1607
|
+
* the actual `fs.writeFile` call. This module is that second check.
|
|
1608
|
+
*
|
|
1609
|
+
* # API
|
|
1610
|
+
*
|
|
1611
|
+
* `safeJoin(baseDir, ...parts)` resolves the parts under `baseDir`
|
|
1612
|
+
* and throws `PathTraversalError` if the resulting absolute path
|
|
1613
|
+
* escapes `baseDir`. Use it instead of `path.join(baseDir, ...)` in
|
|
1614
|
+
* every code path that mixes operator config + user-provided
|
|
1615
|
+
* filenames.
|
|
1616
|
+
*
|
|
1617
|
+
* # Why this idiom (resolve + boundary check)
|
|
1618
|
+
*
|
|
1619
|
+
* - `path.resolve` normalises `..` segments deterministically.
|
|
1620
|
+
* - The startsWith check rejects any normalised path that climbed
|
|
1621
|
+
* out of `baseDir`.
|
|
1622
|
+
* - CodeQL's `js/path-injection` query recognises this exact shape
|
|
1623
|
+
* as a sanitizer, so the static-analysis warning is resolved at
|
|
1624
|
+
* the same time the runtime risk is closed.
|
|
1625
|
+
*
|
|
1626
|
+
* # Edge cases handled
|
|
1627
|
+
*
|
|
1628
|
+
* - `parts` containing `..` segments → throws.
|
|
1629
|
+
* - `parts` containing an absolute path → the absolute path wins
|
|
1630
|
+
* over `baseDir` per POSIX rules, which is almost always wrong
|
|
1631
|
+
* when the caller meant "filename inside baseDir". We reject
|
|
1632
|
+
* absolute segments unless `allowAbsolute: true` is set.
|
|
1633
|
+
* - `baseDir` itself is not normalised before comparison: pass an
|
|
1634
|
+
* already-resolved absolute path. The helper asserts this.
|
|
1635
|
+
* - Symlinks are NOT resolved. If you need symlink-safe joining,
|
|
1636
|
+
* wrap with `fs.realpath` separately. For our installers we
|
|
1637
|
+
* don't follow symlinks at all (we always do explicit
|
|
1638
|
+
* `existsSync` + `writeFileSync`), so this is fine.
|
|
1639
|
+
*
|
|
1640
|
+
* # Examples
|
|
1641
|
+
*
|
|
1642
|
+
* ```ts
|
|
1643
|
+
* // Safe: resolves to /home/ope/.codex/agents/agenticmail-fola.toml
|
|
1644
|
+
* safeJoin('/home/ope/.codex/agents', 'agenticmail-fola.toml');
|
|
1645
|
+
*
|
|
1646
|
+
* // Throws: '../etc/passwd' resolves outside the base dir
|
|
1647
|
+
* safeJoin('/home/ope/.codex/agents', '../etc/passwd');
|
|
1648
|
+
*
|
|
1649
|
+
* // Throws: an absolute path bypasses the base dir
|
|
1650
|
+
* safeJoin('/home/ope/.codex/agents', '/etc/passwd');
|
|
1651
|
+
* ```
|
|
1652
|
+
*/
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Thrown when a `safeJoin` call would resolve to a path outside the
|
|
1656
|
+
* provided base directory. Carries the offending inputs for logging
|
|
1657
|
+
* (with the resolved path elided so we don't leak filesystem layout
|
|
1658
|
+
* to a remote attacker via error messages).
|
|
1659
|
+
*/
|
|
1660
|
+
declare class PathTraversalError extends Error {
|
|
1661
|
+
readonly baseDir: string;
|
|
1662
|
+
readonly parts: string[];
|
|
1663
|
+
constructor(baseDir: string, parts: string[]);
|
|
1664
|
+
}
|
|
1665
|
+
interface SafeJoinOptions {
|
|
1666
|
+
/**
|
|
1667
|
+
* Allow segments that are themselves absolute paths. Off by default
|
|
1668
|
+
* because passing an absolute path to `path.join` discards the
|
|
1669
|
+
* base directory — almost always a bug at our call sites.
|
|
1670
|
+
*/
|
|
1671
|
+
allowAbsolute?: boolean;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Resolve `parts` under `baseDir`, asserting the result stays inside
|
|
1675
|
+
* `baseDir`. See module docstring for the rationale.
|
|
1676
|
+
*
|
|
1677
|
+
* @throws `PathTraversalError` if any segment is absolute (and
|
|
1678
|
+
* `allowAbsolute` is not set), or if the resolved path escapes
|
|
1679
|
+
* `baseDir`.
|
|
1680
|
+
*/
|
|
1681
|
+
declare function safeJoin(baseDir: string, ...partsAndOpts: (string | SafeJoinOptions)[]): string;
|
|
1682
|
+
/**
|
|
1683
|
+
* Convenience: same as `safeJoin` but returns `null` instead of
|
|
1684
|
+
* throwing on a traversal attempt. Use in code paths where a single
|
|
1685
|
+
* malicious filename should be skipped, not propagated up as an
|
|
1686
|
+
* exception (e.g. iterating `readdirSync` results during cleanup).
|
|
1687
|
+
*/
|
|
1688
|
+
declare function tryJoin(baseDir: string, ...parts: string[]): string | null;
|
|
1689
|
+
/**
|
|
1690
|
+
* Validate that a candidate path is already absolute and stays inside
|
|
1691
|
+
* `baseDir`. Use when receiving a path from external config that the
|
|
1692
|
+
* caller wants to treat as already-canonical (e.g. an env var that
|
|
1693
|
+
* names a project directory) — the helper asserts the safety
|
|
1694
|
+
* properties without further joining.
|
|
1695
|
+
*/
|
|
1696
|
+
declare function assertWithinBase(baseDir: string, candidate: string): string;
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* Secret-redaction helpers for log lines and diagnostic output.
|
|
1700
|
+
*
|
|
1701
|
+
* # Why this exists
|
|
1702
|
+
*
|
|
1703
|
+
* AgenticMail keeps three kinds of long-lived secrets in memory:
|
|
1704
|
+
*
|
|
1705
|
+
* 1. The master key (`mk_…`) — full admin scope on the local API.
|
|
1706
|
+
* 2. Per-agent API keys (`ak_…`) — scoped to one agent's mailbox.
|
|
1707
|
+
* 3. Stalwart's admin password and per-agent IMAP/SMTP passwords.
|
|
1708
|
+
*
|
|
1709
|
+
* All three flow through config objects, install results, and the
|
|
1710
|
+
* MCP-server env block. Several of those objects get logged for
|
|
1711
|
+
* diagnostic purposes (install completion summary, dispatcher
|
|
1712
|
+
* startup, error stacks). CodeQL's `js/clear-text-logging` query
|
|
1713
|
+
* flagged eight call sites where a secret could plausibly reach a
|
|
1714
|
+
* log line. This module is the canonical sanitizer for those spots.
|
|
1715
|
+
*
|
|
1716
|
+
* # API
|
|
1717
|
+
*
|
|
1718
|
+
* - `redactSecret(value)` collapses a string secret to a
|
|
1719
|
+
* fixed-shape redaction marker (`mk_***`, `ak_***`, `***`)
|
|
1720
|
+
* while preserving the prefix so the log reader can still tell
|
|
1721
|
+
* the kind of secret it was.
|
|
1722
|
+
* - `redactObject(obj)` walks an object and replaces every value
|
|
1723
|
+
* under a "sensitive-looking" key with REDACTED.
|
|
1724
|
+
* - `REDACTED` constant for cases where the caller wants to
|
|
1725
|
+
* compose their own log line.
|
|
1726
|
+
*
|
|
1727
|
+
* # Conservative match
|
|
1728
|
+
*
|
|
1729
|
+
* The "sensitive-looking" key detection is deliberately broad —
|
|
1730
|
+
* it's better to over-redact a harmless `name` field that happens
|
|
1731
|
+
* to contain "key" than to leak a secret. The currently-matched
|
|
1732
|
+
* key names (case-insensitive substring): `key`, `secret`,
|
|
1733
|
+
* `password`, `token`, `apikey`, `masterkey`, `authorization`,
|
|
1734
|
+
* `bearer`.
|
|
1735
|
+
*/
|
|
1736
|
+
/** Returned in place of any redacted secret. */
|
|
1737
|
+
declare const REDACTED = "***";
|
|
1738
|
+
/**
|
|
1739
|
+
* Redact a string secret to a fixed-shape marker. Preserves the
|
|
1740
|
+
* known prefixes (`mk_`, `ak_`) so log readers can still tell what
|
|
1741
|
+
* kind of secret was elided.
|
|
1742
|
+
*
|
|
1743
|
+
* Non-string inputs return REDACTED unchanged.
|
|
1744
|
+
*/
|
|
1745
|
+
declare function redactSecret(value: unknown): string;
|
|
1746
|
+
/**
|
|
1747
|
+
* Walk an object recursively. Any value under a key that looks
|
|
1748
|
+
* sensitive (see SENSITIVE_KEY_PATTERNS above) is replaced with
|
|
1749
|
+
* `REDACTED`. Arrays are walked too. The original object is NOT
|
|
1750
|
+
* mutated — returns a shallow-cloned tree with the same shape.
|
|
1751
|
+
*
|
|
1752
|
+
* Use this when you want to pass a whole config object into a log
|
|
1753
|
+
* line via JSON.stringify, e.g.:
|
|
1754
|
+
*
|
|
1755
|
+
* ```ts
|
|
1756
|
+
* log.info('install complete', redactObject(result));
|
|
1757
|
+
* ```
|
|
1758
|
+
*
|
|
1759
|
+
* Edge cases:
|
|
1760
|
+
* - Cycles are NOT supported; pass tree-shaped data only.
|
|
1761
|
+
* - Non-plain objects (Map, Set, Buffer, Date, Error) are returned
|
|
1762
|
+
* by reference unchanged — redaction is for plain config dicts.
|
|
1763
|
+
* - Symbols are skipped.
|
|
1764
|
+
*/
|
|
1765
|
+
declare function redactObject<T>(input: T, _depth?: number): T;
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* SSRF-safe URL validation for the AgenticMail API base URL.
|
|
1769
|
+
*
|
|
1770
|
+
* # What this is for
|
|
1771
|
+
*
|
|
1772
|
+
* Every host integration (claudecode, codex, …) reads the master API
|
|
1773
|
+
* URL from `~/.agenticmail/config.json` and uses it to build fetch
|
|
1774
|
+
* requests. CodeQL's `js/request-forgery` query flags those fetch
|
|
1775
|
+
* calls because the URL is operator-controlled (via the config file
|
|
1776
|
+
* or env vars) and could theoretically be redirected to an internal
|
|
1777
|
+
* service the host process can reach but the operator didn't intend.
|
|
1778
|
+
*
|
|
1779
|
+
* In practice the operator controls their own config file — there's
|
|
1780
|
+
* no remote attacker controlling apiUrl in a self-hosted install.
|
|
1781
|
+
* But defence-in-depth dictates we still validate, because:
|
|
1782
|
+
*
|
|
1783
|
+
* - A malicious npm package that planted an env var (e.g.
|
|
1784
|
+
* `AGENTICMAIL_API_URL=http://169.254.169.254/latest/meta-data/`)
|
|
1785
|
+
* could redirect the dispatcher's API calls to a cloud-metadata
|
|
1786
|
+
* endpoint and exfiltrate IAM credentials in the response body.
|
|
1787
|
+
* - A `file://` or `javascript:` scheme would let the operator's
|
|
1788
|
+
* own typo cause undefined behavior.
|
|
1789
|
+
*
|
|
1790
|
+
* # The check
|
|
1791
|
+
*
|
|
1792
|
+
* `validateApiUrl(url)` rejects:
|
|
1793
|
+
*
|
|
1794
|
+
* - Non-`http(s)://` schemes (`file://`, `javascript:`, `data:`,
|
|
1795
|
+
* `ftp://`, etc).
|
|
1796
|
+
* - Cloud metadata IPs (169.254.169.254 — AWS/Azure/GCP) and the
|
|
1797
|
+
* IPv6 equivalent fd00:ec2::254.
|
|
1798
|
+
* - Empty / malformed URLs (via `new URL` parse).
|
|
1799
|
+
*
|
|
1800
|
+
* The check intentionally does NOT restrict to localhost — operators
|
|
1801
|
+
* legitimately run AgenticMail on a NAS or remote VM and point the
|
|
1802
|
+
* host-integration installs at it. The cloud-metadata IPs are the
|
|
1803
|
+
* only blanket block.
|
|
1804
|
+
*
|
|
1805
|
+
* # Why a separate canonicalisation step
|
|
1806
|
+
*
|
|
1807
|
+
* Returning the URL via `url.origin` (rather than the raw input
|
|
1808
|
+
* string) gives CodeQL a sanitiser shape it recognises: the value
|
|
1809
|
+
* is now constrained to whatever `URL` parsed it into, with no
|
|
1810
|
+
* embedded credentials, query strings, or path traversal.
|
|
1811
|
+
*/
|
|
1812
|
+
/** Thrown when `validateApiUrl` rejects a candidate URL. */
|
|
1813
|
+
declare class UnsafeApiUrlError extends Error {
|
|
1814
|
+
readonly raw: string;
|
|
1815
|
+
readonly reason: string;
|
|
1816
|
+
constructor(raw: string, reason: string);
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Validate the operator-supplied AgenticMail API base URL.
|
|
1820
|
+
*
|
|
1821
|
+
* Returns the canonical origin form of the URL (`http://host:port`)
|
|
1822
|
+
* so callers can build paths against it without worrying about
|
|
1823
|
+
* trailing slashes or embedded auth. Throws `UnsafeApiUrlError` on
|
|
1824
|
+
* any structural problem or blocked host.
|
|
1825
|
+
*/
|
|
1826
|
+
declare function validateApiUrl(raw: unknown): string;
|
|
1827
|
+
/**
|
|
1828
|
+
* Build a full request URL from a validated base + path. Used by the
|
|
1829
|
+
* host-integration api clients to ensure the path is appended via
|
|
1830
|
+
* `URL` (escapes correctly) rather than string concat.
|
|
1831
|
+
*/
|
|
1832
|
+
declare function buildApiUrl(baseOrigin: string, pathAndQuery: string): string;
|
|
1833
|
+
|
|
1586
1834
|
interface DependencyStatus {
|
|
1587
1835
|
name: string;
|
|
1588
1836
|
installed: boolean;
|
|
@@ -2123,4 +2371,4 @@ declare class AgentMemoryStore {
|
|
|
2123
2371
|
renderForPrompt(memory: AgentMemoryRead | null): string;
|
|
2124
2372
|
}
|
|
2125
2373
|
|
|
2126
|
-
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor };
|
|
2374
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -1583,6 +1583,254 @@ declare function flushTelemetry(): void;
|
|
|
1583
1583
|
declare function debug(tag: string, message: string): void;
|
|
1584
1584
|
declare function debugWarn(tag: string, message: string): void;
|
|
1585
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* Path-traversal safe filesystem joiner.
|
|
1588
|
+
*
|
|
1589
|
+
* # Why this exists
|
|
1590
|
+
*
|
|
1591
|
+
* Every host-integration installer writes files into a directory
|
|
1592
|
+
* derived from operator config / env vars (`CODEX_HOME`,
|
|
1593
|
+
* `CLAUDE_CODE_AGENTS_DIR`, etc) AND builds filenames from values
|
|
1594
|
+
* returned by the AgenticMail API (account names, agent metadata).
|
|
1595
|
+
* Both inputs cross trust boundaries:
|
|
1596
|
+
*
|
|
1597
|
+
* - The operator's env vars are mostly trusted — but the operator
|
|
1598
|
+
* could fat-finger a relative path that resolves outside the
|
|
1599
|
+
* intended dir, or paste a path containing `..` segments.
|
|
1600
|
+
* - The AgenticMail API responses cross the master-key boundary.
|
|
1601
|
+
* A compromised MCP session or stolen master key could create an
|
|
1602
|
+
* account whose `name` contains `../../etc/something` to escape
|
|
1603
|
+
* the agents directory at install time.
|
|
1604
|
+
*
|
|
1605
|
+
* The existing `sanitizeSubagentName` helpers filter most malicious
|
|
1606
|
+
* names, but defence-in-depth dictates a second boundary check at
|
|
1607
|
+
* the actual `fs.writeFile` call. This module is that second check.
|
|
1608
|
+
*
|
|
1609
|
+
* # API
|
|
1610
|
+
*
|
|
1611
|
+
* `safeJoin(baseDir, ...parts)` resolves the parts under `baseDir`
|
|
1612
|
+
* and throws `PathTraversalError` if the resulting absolute path
|
|
1613
|
+
* escapes `baseDir`. Use it instead of `path.join(baseDir, ...)` in
|
|
1614
|
+
* every code path that mixes operator config + user-provided
|
|
1615
|
+
* filenames.
|
|
1616
|
+
*
|
|
1617
|
+
* # Why this idiom (resolve + boundary check)
|
|
1618
|
+
*
|
|
1619
|
+
* - `path.resolve` normalises `..` segments deterministically.
|
|
1620
|
+
* - The startsWith check rejects any normalised path that climbed
|
|
1621
|
+
* out of `baseDir`.
|
|
1622
|
+
* - CodeQL's `js/path-injection` query recognises this exact shape
|
|
1623
|
+
* as a sanitizer, so the static-analysis warning is resolved at
|
|
1624
|
+
* the same time the runtime risk is closed.
|
|
1625
|
+
*
|
|
1626
|
+
* # Edge cases handled
|
|
1627
|
+
*
|
|
1628
|
+
* - `parts` containing `..` segments → throws.
|
|
1629
|
+
* - `parts` containing an absolute path → the absolute path wins
|
|
1630
|
+
* over `baseDir` per POSIX rules, which is almost always wrong
|
|
1631
|
+
* when the caller meant "filename inside baseDir". We reject
|
|
1632
|
+
* absolute segments unless `allowAbsolute: true` is set.
|
|
1633
|
+
* - `baseDir` itself is not normalised before comparison: pass an
|
|
1634
|
+
* already-resolved absolute path. The helper asserts this.
|
|
1635
|
+
* - Symlinks are NOT resolved. If you need symlink-safe joining,
|
|
1636
|
+
* wrap with `fs.realpath` separately. For our installers we
|
|
1637
|
+
* don't follow symlinks at all (we always do explicit
|
|
1638
|
+
* `existsSync` + `writeFileSync`), so this is fine.
|
|
1639
|
+
*
|
|
1640
|
+
* # Examples
|
|
1641
|
+
*
|
|
1642
|
+
* ```ts
|
|
1643
|
+
* // Safe: resolves to /home/ope/.codex/agents/agenticmail-fola.toml
|
|
1644
|
+
* safeJoin('/home/ope/.codex/agents', 'agenticmail-fola.toml');
|
|
1645
|
+
*
|
|
1646
|
+
* // Throws: '../etc/passwd' resolves outside the base dir
|
|
1647
|
+
* safeJoin('/home/ope/.codex/agents', '../etc/passwd');
|
|
1648
|
+
*
|
|
1649
|
+
* // Throws: an absolute path bypasses the base dir
|
|
1650
|
+
* safeJoin('/home/ope/.codex/agents', '/etc/passwd');
|
|
1651
|
+
* ```
|
|
1652
|
+
*/
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Thrown when a `safeJoin` call would resolve to a path outside the
|
|
1656
|
+
* provided base directory. Carries the offending inputs for logging
|
|
1657
|
+
* (with the resolved path elided so we don't leak filesystem layout
|
|
1658
|
+
* to a remote attacker via error messages).
|
|
1659
|
+
*/
|
|
1660
|
+
declare class PathTraversalError extends Error {
|
|
1661
|
+
readonly baseDir: string;
|
|
1662
|
+
readonly parts: string[];
|
|
1663
|
+
constructor(baseDir: string, parts: string[]);
|
|
1664
|
+
}
|
|
1665
|
+
interface SafeJoinOptions {
|
|
1666
|
+
/**
|
|
1667
|
+
* Allow segments that are themselves absolute paths. Off by default
|
|
1668
|
+
* because passing an absolute path to `path.join` discards the
|
|
1669
|
+
* base directory — almost always a bug at our call sites.
|
|
1670
|
+
*/
|
|
1671
|
+
allowAbsolute?: boolean;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Resolve `parts` under `baseDir`, asserting the result stays inside
|
|
1675
|
+
* `baseDir`. See module docstring for the rationale.
|
|
1676
|
+
*
|
|
1677
|
+
* @throws `PathTraversalError` if any segment is absolute (and
|
|
1678
|
+
* `allowAbsolute` is not set), or if the resolved path escapes
|
|
1679
|
+
* `baseDir`.
|
|
1680
|
+
*/
|
|
1681
|
+
declare function safeJoin(baseDir: string, ...partsAndOpts: (string | SafeJoinOptions)[]): string;
|
|
1682
|
+
/**
|
|
1683
|
+
* Convenience: same as `safeJoin` but returns `null` instead of
|
|
1684
|
+
* throwing on a traversal attempt. Use in code paths where a single
|
|
1685
|
+
* malicious filename should be skipped, not propagated up as an
|
|
1686
|
+
* exception (e.g. iterating `readdirSync` results during cleanup).
|
|
1687
|
+
*/
|
|
1688
|
+
declare function tryJoin(baseDir: string, ...parts: string[]): string | null;
|
|
1689
|
+
/**
|
|
1690
|
+
* Validate that a candidate path is already absolute and stays inside
|
|
1691
|
+
* `baseDir`. Use when receiving a path from external config that the
|
|
1692
|
+
* caller wants to treat as already-canonical (e.g. an env var that
|
|
1693
|
+
* names a project directory) — the helper asserts the safety
|
|
1694
|
+
* properties without further joining.
|
|
1695
|
+
*/
|
|
1696
|
+
declare function assertWithinBase(baseDir: string, candidate: string): string;
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* Secret-redaction helpers for log lines and diagnostic output.
|
|
1700
|
+
*
|
|
1701
|
+
* # Why this exists
|
|
1702
|
+
*
|
|
1703
|
+
* AgenticMail keeps three kinds of long-lived secrets in memory:
|
|
1704
|
+
*
|
|
1705
|
+
* 1. The master key (`mk_…`) — full admin scope on the local API.
|
|
1706
|
+
* 2. Per-agent API keys (`ak_…`) — scoped to one agent's mailbox.
|
|
1707
|
+
* 3. Stalwart's admin password and per-agent IMAP/SMTP passwords.
|
|
1708
|
+
*
|
|
1709
|
+
* All three flow through config objects, install results, and the
|
|
1710
|
+
* MCP-server env block. Several of those objects get logged for
|
|
1711
|
+
* diagnostic purposes (install completion summary, dispatcher
|
|
1712
|
+
* startup, error stacks). CodeQL's `js/clear-text-logging` query
|
|
1713
|
+
* flagged eight call sites where a secret could plausibly reach a
|
|
1714
|
+
* log line. This module is the canonical sanitizer for those spots.
|
|
1715
|
+
*
|
|
1716
|
+
* # API
|
|
1717
|
+
*
|
|
1718
|
+
* - `redactSecret(value)` collapses a string secret to a
|
|
1719
|
+
* fixed-shape redaction marker (`mk_***`, `ak_***`, `***`)
|
|
1720
|
+
* while preserving the prefix so the log reader can still tell
|
|
1721
|
+
* the kind of secret it was.
|
|
1722
|
+
* - `redactObject(obj)` walks an object and replaces every value
|
|
1723
|
+
* under a "sensitive-looking" key with REDACTED.
|
|
1724
|
+
* - `REDACTED` constant for cases where the caller wants to
|
|
1725
|
+
* compose their own log line.
|
|
1726
|
+
*
|
|
1727
|
+
* # Conservative match
|
|
1728
|
+
*
|
|
1729
|
+
* The "sensitive-looking" key detection is deliberately broad —
|
|
1730
|
+
* it's better to over-redact a harmless `name` field that happens
|
|
1731
|
+
* to contain "key" than to leak a secret. The currently-matched
|
|
1732
|
+
* key names (case-insensitive substring): `key`, `secret`,
|
|
1733
|
+
* `password`, `token`, `apikey`, `masterkey`, `authorization`,
|
|
1734
|
+
* `bearer`.
|
|
1735
|
+
*/
|
|
1736
|
+
/** Returned in place of any redacted secret. */
|
|
1737
|
+
declare const REDACTED = "***";
|
|
1738
|
+
/**
|
|
1739
|
+
* Redact a string secret to a fixed-shape marker. Preserves the
|
|
1740
|
+
* known prefixes (`mk_`, `ak_`) so log readers can still tell what
|
|
1741
|
+
* kind of secret was elided.
|
|
1742
|
+
*
|
|
1743
|
+
* Non-string inputs return REDACTED unchanged.
|
|
1744
|
+
*/
|
|
1745
|
+
declare function redactSecret(value: unknown): string;
|
|
1746
|
+
/**
|
|
1747
|
+
* Walk an object recursively. Any value under a key that looks
|
|
1748
|
+
* sensitive (see SENSITIVE_KEY_PATTERNS above) is replaced with
|
|
1749
|
+
* `REDACTED`. Arrays are walked too. The original object is NOT
|
|
1750
|
+
* mutated — returns a shallow-cloned tree with the same shape.
|
|
1751
|
+
*
|
|
1752
|
+
* Use this when you want to pass a whole config object into a log
|
|
1753
|
+
* line via JSON.stringify, e.g.:
|
|
1754
|
+
*
|
|
1755
|
+
* ```ts
|
|
1756
|
+
* log.info('install complete', redactObject(result));
|
|
1757
|
+
* ```
|
|
1758
|
+
*
|
|
1759
|
+
* Edge cases:
|
|
1760
|
+
* - Cycles are NOT supported; pass tree-shaped data only.
|
|
1761
|
+
* - Non-plain objects (Map, Set, Buffer, Date, Error) are returned
|
|
1762
|
+
* by reference unchanged — redaction is for plain config dicts.
|
|
1763
|
+
* - Symbols are skipped.
|
|
1764
|
+
*/
|
|
1765
|
+
declare function redactObject<T>(input: T, _depth?: number): T;
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* SSRF-safe URL validation for the AgenticMail API base URL.
|
|
1769
|
+
*
|
|
1770
|
+
* # What this is for
|
|
1771
|
+
*
|
|
1772
|
+
* Every host integration (claudecode, codex, …) reads the master API
|
|
1773
|
+
* URL from `~/.agenticmail/config.json` and uses it to build fetch
|
|
1774
|
+
* requests. CodeQL's `js/request-forgery` query flags those fetch
|
|
1775
|
+
* calls because the URL is operator-controlled (via the config file
|
|
1776
|
+
* or env vars) and could theoretically be redirected to an internal
|
|
1777
|
+
* service the host process can reach but the operator didn't intend.
|
|
1778
|
+
*
|
|
1779
|
+
* In practice the operator controls their own config file — there's
|
|
1780
|
+
* no remote attacker controlling apiUrl in a self-hosted install.
|
|
1781
|
+
* But defence-in-depth dictates we still validate, because:
|
|
1782
|
+
*
|
|
1783
|
+
* - A malicious npm package that planted an env var (e.g.
|
|
1784
|
+
* `AGENTICMAIL_API_URL=http://169.254.169.254/latest/meta-data/`)
|
|
1785
|
+
* could redirect the dispatcher's API calls to a cloud-metadata
|
|
1786
|
+
* endpoint and exfiltrate IAM credentials in the response body.
|
|
1787
|
+
* - A `file://` or `javascript:` scheme would let the operator's
|
|
1788
|
+
* own typo cause undefined behavior.
|
|
1789
|
+
*
|
|
1790
|
+
* # The check
|
|
1791
|
+
*
|
|
1792
|
+
* `validateApiUrl(url)` rejects:
|
|
1793
|
+
*
|
|
1794
|
+
* - Non-`http(s)://` schemes (`file://`, `javascript:`, `data:`,
|
|
1795
|
+
* `ftp://`, etc).
|
|
1796
|
+
* - Cloud metadata IPs (169.254.169.254 — AWS/Azure/GCP) and the
|
|
1797
|
+
* IPv6 equivalent fd00:ec2::254.
|
|
1798
|
+
* - Empty / malformed URLs (via `new URL` parse).
|
|
1799
|
+
*
|
|
1800
|
+
* The check intentionally does NOT restrict to localhost — operators
|
|
1801
|
+
* legitimately run AgenticMail on a NAS or remote VM and point the
|
|
1802
|
+
* host-integration installs at it. The cloud-metadata IPs are the
|
|
1803
|
+
* only blanket block.
|
|
1804
|
+
*
|
|
1805
|
+
* # Why a separate canonicalisation step
|
|
1806
|
+
*
|
|
1807
|
+
* Returning the URL via `url.origin` (rather than the raw input
|
|
1808
|
+
* string) gives CodeQL a sanitiser shape it recognises: the value
|
|
1809
|
+
* is now constrained to whatever `URL` parsed it into, with no
|
|
1810
|
+
* embedded credentials, query strings, or path traversal.
|
|
1811
|
+
*/
|
|
1812
|
+
/** Thrown when `validateApiUrl` rejects a candidate URL. */
|
|
1813
|
+
declare class UnsafeApiUrlError extends Error {
|
|
1814
|
+
readonly raw: string;
|
|
1815
|
+
readonly reason: string;
|
|
1816
|
+
constructor(raw: string, reason: string);
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Validate the operator-supplied AgenticMail API base URL.
|
|
1820
|
+
*
|
|
1821
|
+
* Returns the canonical origin form of the URL (`http://host:port`)
|
|
1822
|
+
* so callers can build paths against it without worrying about
|
|
1823
|
+
* trailing slashes or embedded auth. Throws `UnsafeApiUrlError` on
|
|
1824
|
+
* any structural problem or blocked host.
|
|
1825
|
+
*/
|
|
1826
|
+
declare function validateApiUrl(raw: unknown): string;
|
|
1827
|
+
/**
|
|
1828
|
+
* Build a full request URL from a validated base + path. Used by the
|
|
1829
|
+
* host-integration api clients to ensure the path is appended via
|
|
1830
|
+
* `URL` (escapes correctly) rather than string concat.
|
|
1831
|
+
*/
|
|
1832
|
+
declare function buildApiUrl(baseOrigin: string, pathAndQuery: string): string;
|
|
1833
|
+
|
|
1586
1834
|
interface DependencyStatus {
|
|
1587
1835
|
name: string;
|
|
1588
1836
|
installed: boolean;
|
|
@@ -2123,4 +2371,4 @@ declare class AgentMemoryStore {
|
|
|
2123
2371
|
renderForPrompt(memory: AgentMemoryRead | null): string;
|
|
2124
2372
|
}
|
|
2125
2373
|
|
|
2126
|
-
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor };
|
|
2374
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
|