@rndmjoker/proton-mcp 0.4.13 → 0.5.1

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.
Files changed (95) hide show
  1. package/README.md +138 -21
  2. package/dist/browser/channel.d.ts +76 -0
  3. package/dist/browser/channel.js +162 -0
  4. package/dist/browser/channel.js.map +1 -0
  5. package/dist/browser/client.d.ts +50 -0
  6. package/dist/browser/client.js +284 -0
  7. package/dist/browser/client.js.map +1 -0
  8. package/dist/browser/connector.d.ts +15 -0
  9. package/dist/browser/connector.js +26 -0
  10. package/dist/browser/connector.js.map +1 -0
  11. package/dist/browser/host.d.ts +50 -0
  12. package/dist/browser/host.js +258 -0
  13. package/dist/browser/host.js.map +1 -0
  14. package/dist/browser/messages.d.ts +163 -0
  15. package/dist/browser/messages.js +338 -0
  16. package/dist/browser/messages.js.map +1 -0
  17. package/dist/browser/paths.d.ts +51 -0
  18. package/dist/browser/paths.js +92 -0
  19. package/dist/browser/paths.js.map +1 -0
  20. package/dist/browser/setup.d.ts +42 -0
  21. package/dist/browser/setup.js +170 -0
  22. package/dist/browser/setup.js.map +1 -0
  23. package/dist/filters/order.d.ts +66 -0
  24. package/dist/filters/order.js +108 -0
  25. package/dist/filters/order.js.map +1 -0
  26. package/dist/filters/sieve.d.ts +184 -0
  27. package/dist/filters/sieve.js +1312 -0
  28. package/dist/filters/sieve.js.map +1 -0
  29. package/dist/instances.d.ts +58 -0
  30. package/dist/instances.js +145 -0
  31. package/dist/instances.js.map +1 -0
  32. package/dist/mail/compose.d.ts +10 -0
  33. package/dist/mail/compose.js.map +1 -1
  34. package/dist/mail/record.d.ts +36 -0
  35. package/dist/mail/record.js +74 -0
  36. package/dist/mail/record.js.map +1 -0
  37. package/dist/permissions.d.ts +139 -0
  38. package/dist/permissions.js +355 -0
  39. package/dist/permissions.js.map +1 -0
  40. package/dist/server.js +330 -16
  41. package/dist/server.js.map +1 -1
  42. package/dist/session.d.ts +12 -0
  43. package/dist/session.js +23 -0
  44. package/dist/session.js.map +1 -1
  45. package/dist/settings.d.ts +81 -11
  46. package/dist/settings.js +160 -25
  47. package/dist/settings.js.map +1 -1
  48. package/dist/tools/confirm.d.ts +4 -2
  49. package/dist/tools/confirm.js +12 -3
  50. package/dist/tools/confirm.js.map +1 -1
  51. package/dist/tools/failures.d.ts +6 -8
  52. package/dist/tools/failures.js +36 -17
  53. package/dist/tools/failures.js.map +1 -1
  54. package/dist/tools/filter-tools.d.ts +82 -0
  55. package/dist/tools/filter-tools.js +729 -0
  56. package/dist/tools/filter-tools.js.map +1 -0
  57. package/dist/tools/filters.d.ts +8 -1
  58. package/dist/tools/filters.js +24 -12
  59. package/dist/tools/filters.js.map +1 -1
  60. package/dist/tools/format.d.ts +15 -0
  61. package/dist/tools/format.js +4 -3
  62. package/dist/tools/format.js.map +1 -1
  63. package/dist/tools/guidance.js +12 -7
  64. package/dist/tools/guidance.js.map +1 -1
  65. package/dist/tools/interface.d.ts +20 -6
  66. package/dist/tools/interface.js +40 -16
  67. package/dist/tools/interface.js.map +1 -1
  68. package/dist/tools/send.d.ts +9 -0
  69. package/dist/tools/send.js +51 -9
  70. package/dist/tools/send.js.map +1 -1
  71. package/dist/tools/sign-in-required.d.ts +15 -2
  72. package/dist/tools/sign-in-required.js +30 -2
  73. package/dist/tools/sign-in-required.js.map +1 -1
  74. package/dist/web/layout.d.ts +12 -5
  75. package/dist/web/layout.js +32 -9
  76. package/dist/web/layout.js.map +1 -1
  77. package/dist/web/pages.d.ts +4 -2
  78. package/dist/web/pages.js +10 -9
  79. package/dist/web/pages.js.map +1 -1
  80. package/dist/web/pending.d.ts +9 -6
  81. package/dist/web/pending.js +9 -7
  82. package/dist/web/pending.js.map +1 -1
  83. package/dist/web/preview-server.d.ts +33 -0
  84. package/dist/web/preview-server.js +102 -0
  85. package/dist/web/preview-server.js.map +1 -0
  86. package/dist/web/sections.d.ts +86 -1
  87. package/dist/web/sections.js +290 -7
  88. package/dist/web/sections.js.map +1 -1
  89. package/dist/web/security.d.ts +48 -13
  90. package/dist/web/security.js +68 -16
  91. package/dist/web/security.js.map +1 -1
  92. package/dist/web/server.d.ts +81 -24
  93. package/dist/web/server.js +478 -117
  94. package/dist/web/server.js.map +1 -1
  95. package/package.json +1 -1
@@ -3,15 +3,19 @@
3
3
  *
4
4
  * The threat model is not "someone on the internet": the server binds to
5
5
  * loopback only, so nobody reaches it from outside. What has to be defended
6
- * against is anything else running on the same machine, and anything the user's
7
- * browser is talked into doing by a page from elsewhere.
6
+ * against is anything the user's browser is talked into doing by a page from
7
+ * elsewhere, and, when the user sets one, anything on the same machine that does
8
+ * not know the PIN.
8
9
  *
9
10
  * Four defences, each against a specific attack:
10
11
  *
11
- * 1. **A session token in the URL.** Any local program can send a request to
12
- * 127.0.0.1. Without a secret it could drive the interface, read the state
13
- * and store credentials of its choosing. The token is generated per process
14
- * and never written to disk.
12
+ * 1. **An optional PIN on every change.** Any local program can send a request
13
+ * to 127.0.0.1, and so can the assistant, through a browser tool. Until
14
+ * 2026-11-05 a token in the address kept them out, but the address reached
15
+ * the assistant through `open_configuration`, so the token protected against
16
+ * everything except the one party the switches for autonomy are about. A PIN
17
+ * the user types and the assistant never sees does. It is optional: without
18
+ * one, anything on this machine can change the settings, and the pages say so.
15
19
  * 2. **Host header check.** A page on the internet can resolve its own domain to
16
20
  * 127.0.0.1 and have the browser send requests there. The browser then treats
17
21
  * that domain as the origin, so same-origin rules do not help. Rejecting any
@@ -30,13 +34,44 @@
30
34
  */
31
35
  import type { IncomingMessage } from 'node:http';
32
36
  export interface SessionSecrets {
33
- /** Grants access at all. Part of the URL the user opens. */
34
- accessToken: string;
35
37
  /** Signs CSRF tokens. Never leaves the process. */
36
38
  csrfSecret: string;
37
39
  }
38
40
  /** Creates the per-process secrets. */
39
41
  export declare function createSession(): SessionSecrets;
42
+ /**
43
+ * A PIN as it is stored: salted and run through scrypt, never the PIN itself.
44
+ *
45
+ * The settings file is readable by the user's own programs, the assistant
46
+ * included when it can read files. A plain PIN there would be a PIN the
47
+ * assistant can look up. A hash of a short PIN can still be guessed offline,
48
+ * which is why the length rule below asks for more than four digits and why
49
+ * scrypt rather than a fast hash: every guess has to pay for the memory.
50
+ */
51
+ export interface StoredPin {
52
+ salt: string;
53
+ hash: string;
54
+ }
55
+ /** Why a PIN cannot be used, or nothing when it can. */
56
+ export declare function pinProblem(pin: string): string | undefined;
57
+ export declare function hashPin(pin: string): Promise<StoredPin>;
58
+ export declare function pinMatches(pin: string, stored: StoredPin): Promise<boolean>;
59
+ /**
60
+ * Slows down guessing.
61
+ *
62
+ * After `max` wrong PINs within `windowMs`, every attempt is refused until the
63
+ * window has passed, the right PIN included: a lock that the right answer opens
64
+ * early tells the guesser when they found it. Held in memory by the server that
65
+ * shows the interface, which is the only one that checks PINs.
66
+ */
67
+ export declare class AttemptLimiter {
68
+ #private;
69
+ constructor(max?: number, windowMs?: number);
70
+ /** Whether an attempt may be checked at all right now. */
71
+ allowed(now?: number): boolean;
72
+ fail(now?: number): void;
73
+ reset(): void;
74
+ }
40
75
  /** Compares two secrets without leaking their similarity through timing. */
41
76
  export declare function safeEqual(a: string, b: string): boolean;
42
77
  /**
@@ -64,7 +99,7 @@ export declare function isAllowedOrigin(origin: string | undefined, port: number
64
99
  export declare function csrfToken(secrets: SessionSecrets, purpose: string): string;
65
100
  export declare function csrfValid(secrets: SessionSecrets, purpose: string, presented: string): boolean;
66
101
  /** Why a request was rejected. Deliberately vague towards the client. */
67
- export type RejectionReason = 'bad-host' | 'bad-origin' | 'bad-token' | 'bad-csrf' | 'method-not-allowed';
102
+ export type RejectionReason = 'bad-host' | 'bad-origin' | 'bad-csrf' | 'method-not-allowed';
68
103
  export interface CheckResult {
69
104
  ok: boolean;
70
105
  reason?: RejectionReason;
@@ -73,8 +108,6 @@ export interface CheckOptions {
73
108
  request: IncomingMessage;
74
109
  secrets: SessionSecrets;
75
110
  port: number;
76
- /** The token presented by the client, from the query string or a header. */
77
- presentedToken: string | undefined;
78
111
  /** True for requests that change something. */
79
112
  writing: boolean;
80
113
  /** For writing requests: the CSRF token and what it is for. */
@@ -86,8 +119,10 @@ export interface CheckOptions {
86
119
  /**
87
120
  * Runs every check in order.
88
121
  *
89
- * Order matters: the host check comes before the token check, so a rebinding
90
- * attempt is refused before it can even try tokens.
122
+ * Order matters: the host check comes first, so a rebinding attempt is refused
123
+ * before anything else is looked at. The PIN is not checked here: a wrong PIN is
124
+ * the user's mistake and gets a page that says so, where every failure in here
125
+ * gets the same refusal that says nothing.
91
126
  */
92
127
  export declare function checkRequest(options: CheckOptions): CheckResult;
93
128
  /**
@@ -3,15 +3,19 @@
3
3
  *
4
4
  * The threat model is not "someone on the internet": the server binds to
5
5
  * loopback only, so nobody reaches it from outside. What has to be defended
6
- * against is anything else running on the same machine, and anything the user's
7
- * browser is talked into doing by a page from elsewhere.
6
+ * against is anything the user's browser is talked into doing by a page from
7
+ * elsewhere, and, when the user sets one, anything on the same machine that does
8
+ * not know the PIN.
8
9
  *
9
10
  * Four defences, each against a specific attack:
10
11
  *
11
- * 1. **A session token in the URL.** Any local program can send a request to
12
- * 127.0.0.1. Without a secret it could drive the interface, read the state
13
- * and store credentials of its choosing. The token is generated per process
14
- * and never written to disk.
12
+ * 1. **An optional PIN on every change.** Any local program can send a request
13
+ * to 127.0.0.1, and so can the assistant, through a browser tool. Until
14
+ * 2026-11-05 a token in the address kept them out, but the address reached
15
+ * the assistant through `open_configuration`, so the token protected against
16
+ * everything except the one party the switches for autonomy are about. A PIN
17
+ * the user types and the assistant never sees does. It is optional: without
18
+ * one, anything on this machine can change the settings, and the pages say so.
15
19
  * 2. **Host header check.** A page on the internet can resolve its own domain to
16
20
  * 127.0.0.1 and have the browser send requests there. The browser then treats
17
21
  * that domain as the origin, so same-origin rules do not help. Rejecting any
@@ -28,18 +32,67 @@
28
32
  * All comparisons of secrets use timingSafeEqual, so response time does not
29
33
  * reveal how much of a token was right.
30
34
  */
31
- import { randomBytes, timingSafeEqual, createHmac } from 'node:crypto';
35
+ import { randomBytes, timingSafeEqual, createHmac, scrypt } from 'node:crypto';
32
36
  /** Hostnames that mean this machine. Anything else is rejected. */
33
37
  const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
34
38
  /** Creates the per-process secrets. */
35
39
  export function createSession() {
36
40
  return {
37
- // 32 bytes base64url: long enough that guessing is hopeless, short enough
38
- // to survive being copied into a browser.
39
- accessToken: randomBytes(32).toString('base64url'),
40
41
  csrfSecret: randomBytes(32).toString('base64url'),
41
42
  };
42
43
  }
44
+ /** scrypt with Node's default cost, named so a change here is deliberate. */
45
+ const PIN_KEY_LENGTH = 32;
46
+ const PIN_COST = { N: 16384, r: 8, p: 1 };
47
+ function derive(pin, salt) {
48
+ return new Promise((resolve, reject) => {
49
+ scrypt(pin.normalize('NFC'), salt, PIN_KEY_LENGTH, PIN_COST, (error, key) => error ? reject(error) : resolve(key));
50
+ });
51
+ }
52
+ /** Why a PIN cannot be used, or nothing when it can. */
53
+ export function pinProblem(pin) {
54
+ if (pin.length < 6)
55
+ return 'The PIN needs at least 6 characters.';
56
+ if (pin.length > 128)
57
+ return 'The PIN can have at most 128 characters.';
58
+ return undefined;
59
+ }
60
+ export async function hashPin(pin) {
61
+ const salt = randomBytes(16).toString('base64url');
62
+ return { salt, hash: (await derive(pin, salt)).toString('base64url') };
63
+ }
64
+ export async function pinMatches(pin, stored) {
65
+ const presented = await derive(pin, stored.salt);
66
+ return safeEqual(presented.toString('base64url'), stored.hash);
67
+ }
68
+ /**
69
+ * Slows down guessing.
70
+ *
71
+ * After `max` wrong PINs within `windowMs`, every attempt is refused until the
72
+ * window has passed, the right PIN included: a lock that the right answer opens
73
+ * early tells the guesser when they found it. Held in memory by the server that
74
+ * shows the interface, which is the only one that checks PINs.
75
+ */
76
+ export class AttemptLimiter {
77
+ #failures = [];
78
+ #max;
79
+ #windowMs;
80
+ constructor(max = 5, windowMs = 60_000) {
81
+ this.#max = max;
82
+ this.#windowMs = windowMs;
83
+ }
84
+ /** Whether an attempt may be checked at all right now. */
85
+ allowed(now = Date.now()) {
86
+ this.#failures = this.#failures.filter((at) => now - at < this.#windowMs);
87
+ return this.#failures.length < this.#max;
88
+ }
89
+ fail(now = Date.now()) {
90
+ this.#failures.push(now);
91
+ }
92
+ reset() {
93
+ this.#failures = [];
94
+ }
95
+ }
43
96
  /** Compares two secrets without leaking their similarity through timing. */
44
97
  export function safeEqual(a, b) {
45
98
  const left = Buffer.from(a, 'utf8');
@@ -109,16 +162,15 @@ export function csrfValid(secrets, purpose, presented) {
109
162
  /**
110
163
  * Runs every check in order.
111
164
  *
112
- * Order matters: the host check comes before the token check, so a rebinding
113
- * attempt is refused before it can even try tokens.
165
+ * Order matters: the host check comes first, so a rebinding attempt is refused
166
+ * before anything else is looked at. The PIN is not checked here: a wrong PIN is
167
+ * the user's mistake and gets a page that says so, where every failure in here
168
+ * gets the same refusal that says nothing.
114
169
  */
115
170
  export function checkRequest(options) {
116
- const { request, secrets, port, presentedToken, writing, csrf } = options;
171
+ const { request, secrets, port, writing, csrf } = options;
117
172
  if (!isAllowedHost(request.headers.host))
118
173
  return { ok: false, reason: 'bad-host' };
119
- if (!presentedToken || !safeEqual(secrets.accessToken, presentedToken)) {
120
- return { ok: false, reason: 'bad-token' };
121
- }
122
174
  if (writing) {
123
175
  if (!isAllowedOrigin(request.headers.origin, port))
124
176
  return { ok: false, reason: 'bad-origin' };
@@ -1 +1 @@
1
- {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/web/security.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/web/security.ts"],"names":[],"mappings":"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"}
@@ -12,30 +12,68 @@
12
12
  */
13
13
  import { type IncomingMessage } from 'node:http';
14
14
  import type { Credentials, CredentialStore, StoreKind } from '../credentials/store.js';
15
- import { type PendingData } from './pending.js';
15
+ import { type StoredPin } from './security.js';
16
+ import { type ServerSummary, type ToolLevelView, type LimitsView, type RecordView, type FilterActionsView } from './sections.js';
17
+ import type { Level, SendLimits, RecordCopy } from '../settings.js';
18
+ import { type Unlockable } from '../filters/sieve.js';
16
19
  /**
17
- * The port tried first.
20
+ * The one address of the interface, shared by every running server.
18
21
  *
19
- * A fixed default rather than a random one, because the interface runs for as
20
- * long as the server does and is meant to be opened again and again. A port that
21
- * moved on every start would make that needlessly awkward.
22
+ * Fixed rather than random, and since 2026-11-05 also not walked upwards when
23
+ * taken: every client session starts its own server, and the interface they
24
+ * share has to be at the same address for all of them. The first server to
25
+ * start serves it; the others take over when it stops.
22
26
  */
23
27
  export declare const DEFAULT_WEB_PORT = 7345;
28
+ /** How often a server that does not serve the interface tries to take it over. */
29
+ export declare const TAKEOVER_INTERVAL_MS = 10000;
24
30
  /**
25
- * How many consecutive ports to try.
31
+ * Who is on the port: another proton-mcp, an older one, something else, or
32
+ * nobody.
26
33
  *
27
- * Several clients may each start their own server, and the second one must not
28
- * fail over a port the first one took. Failing outright would be worse than
29
- * moving up by one.
34
+ * "older" is a proton-mcp from before the shared interface, 0.4 and earlier.
35
+ * Each of those served an interface of its own, with a token in the address,
36
+ * and knows nothing of the others. One still running in a session started
37
+ * before an update holds the port until that session ends.
30
38
  */
31
- export declare const PORT_ATTEMPTS = 10;
39
+ export type PortOccupant = 'proton-mcp' | 'older' | 'other' | 'none';
32
40
  export interface WebInterfaceOptions {
33
41
  /**
34
- * Left out, the interface starts at DEFAULT_WEB_PORT and walks upwards past
35
- * anything already listening. 0 lets the operating system pick a free port,
36
- * which the tests use to avoid collisions; it is not the default.
42
+ * Left out, the interface uses DEFAULT_WEB_PORT. 0 lets the operating system
43
+ * pick a free port, which the tests use to avoid collisions; there is no
44
+ * takeover then, since no other server could know the address.
37
45
  */
38
46
  port?: number;
47
+ /** The PIN as stored, or nothing while none is set. Read on every request. */
48
+ getPin?: () => StoredPin | undefined;
49
+ /** Stores a new PIN, or removes it with undefined. Returns an error to show. */
50
+ onPinChange?: (next: StoredPin | undefined) => Promise<string | undefined>;
51
+ /** Every running server, for the overview. */
52
+ getServers?: () => ServerSummary[];
53
+ /** Told whenever this server starts or stops serving the interface. */
54
+ onServingChange?: (serving: boolean) => void;
55
+ /** How often to try taking over the port. TAKEOVER_INTERVAL_MS unless a test says otherwise. */
56
+ takeoverIntervalMs?: number;
57
+ /** What each tool may do and the bounds on sending without asking, for the permissions page. */
58
+ getPermissions?: () => {
59
+ tools: ToolLevelView[];
60
+ limits: LimitsView;
61
+ };
62
+ /**
63
+ * Stores the levels chosen on the page, only those not fixed by the
64
+ * environment. Returns an error to show, or nothing.
65
+ */
66
+ onLevels?: (levels: Record<string, Level>) => Promise<string | undefined>;
67
+ /** Stores the bounds on sending without asking. Returns an error to show, or nothing. */
68
+ onLimits?: (limits: SendLimits) => Promise<string | undefined>;
69
+ /** The recipient added to every message sent, for the pages. */
70
+ getRecord?: () => RecordView;
71
+ /** Stores it, or removes it with undefined. Returns an error to show, or nothing. */
72
+ onRecord?: (record: RecordCopy | undefined) => Promise<string | undefined>;
73
+ /** What a filter from an assistant may do beyond filing and flags, for the pages. */
74
+ getFilterActions?: () => FilterActionsView;
75
+ /** Stores it. Returns an error to show, or nothing. */
76
+ onFilterActions?: (actions: Unlockable[]) => Promise<string | undefined>;
39
77
  /**
40
78
  * Called when the user signs in. Returns an error message to show, or nothing
41
79
  * on success. The master password is only set for the encrypted-file store.
@@ -110,14 +148,6 @@ export interface WebInterfaceOptions {
110
148
  noticeDismissed?: () => boolean;
111
149
  /** Remembers a dismissal. Without it the notice has no button. */
112
150
  onDismissNotice?: () => Promise<void>;
113
- /**
114
- * The message behind a pending confirmation, if one is waiting.
115
- *
116
- * Absent when nothing can be previewed, which makes the page say so rather
117
- * than fail. What it returns is decrypted mail, so the caller holds it in
118
- * memory only and drops it as soon as the question is answered.
119
- */
120
- getPending?: (digest: string) => Omit<PendingData, 'token' | 'disclaimer'> | undefined;
121
151
  /** Where notices go. stderr, never stdout, which carries the MCP protocol. */
122
152
  notify?: (message: string) => void;
123
153
  }
@@ -165,13 +195,40 @@ export declare function readBody(request: IncomingMessage, maxBytes?: number): P
165
195
  export declare class WebInterface {
166
196
  #private;
167
197
  constructor(options: WebInterfaceOptions);
168
- /** The URL the user has to open, including the access token. */
198
+ /**
199
+ * The address of the interface. The same for every running server, whichever
200
+ * of them serves it, and nothing in it is secret.
201
+ */
169
202
  get url(): string;
170
203
  get port(): number;
204
+ /** True while this server serves the interface itself. */
171
205
  get running(): boolean;
206
+ /**
207
+ * Whether the interface can be reached at `url`, from this server or from
208
+ * another running proton-mcp. False when something else holds the port.
209
+ */
210
+ get reachable(): boolean;
211
+ /** Who held the port when this server last found it taken. */
212
+ get occupant(): PortOccupant;
213
+ /**
214
+ * Asks whoever holds the port whether it is a proton-mcp.
215
+ *
216
+ * The answer decides between "another session serves the interface, all is
217
+ * well" and "something else sits on this port", which the user has to hear
218
+ * about, because then nobody serves the interface at all. An older proton-mcp
219
+ * is the third answer: no interface either, but the remedy is to restart the
220
+ * sessions that still run it, not to move every server to another port.
221
+ */
222
+ static probe(port: number, timeoutMs?: number): Promise<PortOccupant>;
223
+ /**
224
+ * Serves the interface, or leaves it to the server that already does.
225
+ *
226
+ * Never throws over a taken port. Another proton-mcp on it is the ordinary
227
+ * case with several sessions; this server then tries every few seconds to
228
+ * take over, so the interface survives the session that started it. Anything
229
+ * else on the port is reported once, and taken over too if it ever leaves.
230
+ */
172
231
  start(): Promise<string>;
173
232
  stop(): Promise<void>;
174
- /** Invalidates the current link and issues a new one. */
175
- rotateToken(): string;
176
233
  }
177
234
  export type { CredentialStore };