@grackle-ai/server 0.72.1 → 0.72.3

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 (45) hide show
  1. package/dist/grpc-service.d.ts.map +1 -1
  2. package/dist/grpc-service.js +3 -5
  3. package/dist/grpc-service.js.map +1 -1
  4. package/dist/index.js +19 -16
  5. package/dist/index.js.map +1 -1
  6. package/dist/ws-bridge.d.ts.map +1 -1
  7. package/dist/ws-bridge.js +2 -3
  8. package/dist/ws-bridge.js.map +1 -1
  9. package/package.json +12 -7
  10. package/dist/adapters/codespace.d.ts +0 -40
  11. package/dist/adapters/codespace.d.ts.map +0 -1
  12. package/dist/adapters/codespace.js +0 -212
  13. package/dist/adapters/codespace.js.map +0 -1
  14. package/dist/adapters/docker.d.ts +0 -39
  15. package/dist/adapters/docker.d.ts.map +0 -1
  16. package/dist/adapters/docker.js +0 -366
  17. package/dist/adapters/docker.js.map +0 -1
  18. package/dist/adapters/local.d.ts +0 -15
  19. package/dist/adapters/local.d.ts.map +0 -1
  20. package/dist/adapters/local.js +0 -57
  21. package/dist/adapters/local.js.map +0 -1
  22. package/dist/adapters/ssh.d.ts +0 -45
  23. package/dist/adapters/ssh.d.ts.map +0 -1
  24. package/dist/adapters/ssh.js +0 -214
  25. package/dist/adapters/ssh.js.map +0 -1
  26. package/dist/api-key.d.ts +0 -8
  27. package/dist/api-key.d.ts.map +0 -1
  28. package/dist/api-key.js +0 -58
  29. package/dist/api-key.js.map +0 -1
  30. package/dist/oauth.d.ts +0 -100
  31. package/dist/oauth.d.ts.map +0 -1
  32. package/dist/oauth.js +0 -225
  33. package/dist/oauth.js.map +0 -1
  34. package/dist/pairing.d.ts +0 -22
  35. package/dist/pairing.d.ts.map +0 -1
  36. package/dist/pairing.js +0 -136
  37. package/dist/pairing.js.map +0 -1
  38. package/dist/security-headers.d.ts +0 -17
  39. package/dist/security-headers.d.ts.map +0 -1
  40. package/dist/security-headers.js +0 -31
  41. package/dist/security-headers.js.map +0 -1
  42. package/dist/session.d.ts +0 -36
  43. package/dist/session.d.ts.map +0 -1
  44. package/dist/session.js +0 -143
  45. package/dist/session.js.map +0 -1
package/dist/pairing.d.ts DELETED
@@ -1,22 +0,0 @@
1
- /** Start the periodic cleanup timer. Call once on server startup. */
2
- export declare function startPairingCleanup(): void;
3
- /** Stop the periodic cleanup timer. */
4
- export declare function stopPairingCleanup(): void;
5
- /**
6
- * Generate a new pairing code.
7
- *
8
- * Returns the 6-character uppercase alphanumeric code, or undefined
9
- * if the maximum number of active codes has been reached.
10
- */
11
- export declare function generatePairingCode(): string | undefined;
12
- /**
13
- * Attempt to redeem a pairing code. Returns true if the code was valid
14
- * and has been consumed (burned). Returns false otherwise.
15
- *
16
- * @param code - The pairing code to redeem (case-insensitive).
17
- * @param remoteIp - The remote IP address for rate limiting.
18
- */
19
- export declare function redeemPairingCode(code: string, remoteIp: string): boolean;
20
- /** Clear all pairing codes and rate limits (for testing). */
21
- export declare function clearPairing(): void;
22
- //# sourceMappingURL=pairing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"AA4CA,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,IAAI,CAmB1C;AAED,uCAAuC;AACvC,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAgCxD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAuCzE;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,IAAI,IAAI,CAGnC"}
package/dist/pairing.js DELETED
@@ -1,136 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { logger } from "./logger.js";
3
- /** How long a pairing code is valid after generation. */
4
- const PAIRING_CODE_TTL_MS = 5 * 60 * 1000;
5
- /** Maximum number of active (unexpired) pairing codes. */
6
- const MAX_ACTIVE_CODES = 10;
7
- /** Number of characters in a pairing code. */
8
- const PAIRING_CODE_LENGTH = 6;
9
- /** Maximum failed redemption attempts per IP before blocking. */
10
- const MAX_FAILED_ATTEMPTS = 5;
11
- /** Duration to block an IP after exceeding failed attempts. */
12
- const RATE_LIMIT_WINDOW_MS = 60 * 1000;
13
- /** Duration to block an IP after exceeding the rate limit. */
14
- const RATE_LIMIT_BLOCK_MS = 5 * 60 * 1000;
15
- /** Interval at which expired codes and rate-limit entries are cleaned up. */
16
- const CLEANUP_INTERVAL_MS = 60 * 1000;
17
- /** Active pairing codes keyed by code string (uppercase). */
18
- const activeCodes = new Map();
19
- /** Rate-limit tracking keyed by remote IP. */
20
- const rateLimits = new Map();
21
- let cleanupTimer;
22
- /** Start the periodic cleanup timer. Call once on server startup. */
23
- export function startPairingCleanup() {
24
- if (cleanupTimer) {
25
- return;
26
- }
27
- cleanupTimer = setInterval(() => {
28
- const now = Date.now();
29
- for (const [code, record] of activeCodes) {
30
- if (now > record.expiresAt) {
31
- activeCodes.delete(code);
32
- }
33
- }
34
- for (const [ip, record] of rateLimits) {
35
- if (now > record.blockedUntil && now - record.firstAttempt > RATE_LIMIT_WINDOW_MS) {
36
- rateLimits.delete(ip);
37
- }
38
- }
39
- }, CLEANUP_INTERVAL_MS);
40
- // Allow the process to exit even if the timer is still running
41
- cleanupTimer.unref();
42
- }
43
- /** Stop the periodic cleanup timer. */
44
- export function stopPairingCleanup() {
45
- if (cleanupTimer) {
46
- clearInterval(cleanupTimer);
47
- cleanupTimer = undefined;
48
- }
49
- }
50
- /**
51
- * Generate a new pairing code.
52
- *
53
- * Returns the 6-character uppercase alphanumeric code, or undefined
54
- * if the maximum number of active codes has been reached.
55
- */
56
- export function generatePairingCode() {
57
- // Purge expired codes first
58
- const now = Date.now();
59
- for (const [code, record] of activeCodes) {
60
- if (now > record.expiresAt) {
61
- activeCodes.delete(code);
62
- }
63
- }
64
- if (activeCodes.size >= MAX_ACTIVE_CODES) {
65
- logger.warn("Maximum active pairing codes reached (%d)", MAX_ACTIVE_CODES);
66
- return undefined;
67
- }
68
- // Generate a code that doesn't collide with existing ones
69
- let code;
70
- do {
71
- code = randomBytes(4)
72
- .toString("base64url")
73
- .replace(/[^A-Za-z0-9]/g, "")
74
- .slice(0, PAIRING_CODE_LENGTH)
75
- .toUpperCase();
76
- } while (code.length < PAIRING_CODE_LENGTH || activeCodes.has(code));
77
- const record = {
78
- code,
79
- createdAt: now,
80
- expiresAt: now + PAIRING_CODE_TTL_MS,
81
- };
82
- activeCodes.set(code, record);
83
- logger.info({ expiresIn: PAIRING_CODE_TTL_MS / 1000 }, "Generated pairing code");
84
- return code;
85
- }
86
- /**
87
- * Attempt to redeem a pairing code. Returns true if the code was valid
88
- * and has been consumed (burned). Returns false otherwise.
89
- *
90
- * @param code - The pairing code to redeem (case-insensitive).
91
- * @param remoteIp - The remote IP address for rate limiting.
92
- */
93
- export function redeemPairingCode(code, remoteIp) {
94
- const now = Date.now();
95
- const normalised = code.toUpperCase().trim();
96
- // Check rate limit
97
- const limit = rateLimits.get(remoteIp);
98
- if (limit && now < limit.blockedUntil) {
99
- logger.warn({ remoteIp }, "Pairing attempt blocked by rate limit");
100
- return false;
101
- }
102
- const record = activeCodes.get(normalised);
103
- if (!record || now > record.expiresAt) {
104
- // Record failed attempt
105
- if (limit) {
106
- if (now - limit.firstAttempt > RATE_LIMIT_WINDOW_MS) {
107
- // Reset window
108
- rateLimits.set(remoteIp, { attempts: 1, firstAttempt: now, blockedUntil: 0 });
109
- }
110
- else {
111
- limit.attempts++;
112
- if (limit.attempts >= MAX_FAILED_ATTEMPTS) {
113
- limit.blockedUntil = now + RATE_LIMIT_BLOCK_MS;
114
- logger.warn({ remoteIp, attempts: limit.attempts }, "Rate limit triggered for pairing attempts");
115
- }
116
- }
117
- }
118
- else {
119
- rateLimits.set(remoteIp, { attempts: 1, firstAttempt: now, blockedUntil: 0 });
120
- }
121
- if (record && now > record.expiresAt) {
122
- activeCodes.delete(normalised);
123
- }
124
- return false;
125
- }
126
- // Burn the code — single use
127
- activeCodes.delete(normalised);
128
- logger.info("Pairing code redeemed");
129
- return true;
130
- }
131
- /** Clear all pairing codes and rate limits (for testing). */
132
- export function clearPairing() {
133
- activeCodes.clear();
134
- rateLimits.clear();
135
- }
136
- //# sourceMappingURL=pairing.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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"}
@@ -1,17 +0,0 @@
1
- import type { ServerResponse } from "node:http";
2
- /**
3
- * Content Security Policy for the web handler.
4
- *
5
- * Covers the React SPA (served from 'self') and server-rendered pages
6
- * (pairing/authorize) which use inline styles.
7
- */
8
- export declare const WEB_CONTENT_SECURITY_POLICY: string;
9
- /**
10
- * Set defense-in-depth security headers on every web response.
11
- *
12
- * Called at the top of `createWebHandler`'s returned function so that all
13
- * response paths (static files, HTML pages, JSON APIs, redirects) are covered
14
- * without modifying each `writeHead` call individually.
15
- */
16
- export declare function setSecurityHeaders(res: ServerResponse): void;
17
- //# sourceMappingURL=security-headers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../src/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAW9B,CAAC;AAEb;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAI5D"}
@@ -1,31 +0,0 @@
1
- /**
2
- * Content Security Policy for the web handler.
3
- *
4
- * Covers the React SPA (served from 'self') and server-rendered pages
5
- * (pairing/authorize) which use inline styles.
6
- */
7
- export const WEB_CONTENT_SECURITY_POLICY = [
8
- "default-src 'self'",
9
- "script-src 'self'",
10
- "style-src 'self' 'unsafe-inline'",
11
- "img-src 'self' data:",
12
- "font-src 'self'",
13
- "connect-src 'self'",
14
- "object-src 'none'",
15
- "form-action 'self'",
16
- "frame-ancestors 'none'",
17
- "base-uri 'self'",
18
- ].join("; ");
19
- /**
20
- * Set defense-in-depth security headers on every web response.
21
- *
22
- * Called at the top of `createWebHandler`'s returned function so that all
23
- * response paths (static files, HTML pages, JSON APIs, redirects) are covered
24
- * without modifying each `writeHead` call individually.
25
- */
26
- export function setSecurityHeaders(res) {
27
- res.setHeader("X-Content-Type-Options", "nosniff");
28
- res.setHeader("X-Frame-Options", "DENY");
29
- res.setHeader("Content-Security-Policy", WEB_CONTENT_SECURITY_POLICY);
30
- }
31
- //# sourceMappingURL=security-headers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../src/security-headers.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAW;IACjD,oBAAoB;IACpB,mBAAmB;IACnB,kCAAkC;IAClC,sBAAsB;IACtB,iBAAiB;IACjB,oBAAoB;IACpB,mBAAmB;IACnB,oBAAoB;IACpB,wBAAwB;IACxB,iBAAiB;CAClB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAmB;IACpD,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;IACnD,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;IACzC,GAAG,CAAC,SAAS,CAAC,yBAAyB,EAAE,2BAA2B,CAAC,CAAC;AACxE,CAAC"}
package/dist/session.d.ts DELETED
@@ -1,36 +0,0 @@
1
- /** Name of the session cookie sent to browsers. */
2
- export declare const SESSION_COOKIE_NAME: string;
3
- /** Start the periodic session cleanup timer. Call once on server startup. */
4
- export declare function startSessionCleanup(): void;
5
- /** Stop the periodic session cleanup timer. */
6
- export declare function stopSessionCleanup(): void;
7
- /**
8
- * Create a new session and return the Set-Cookie header value.
9
- *
10
- * The cookie format is `<sessionId>.<signature>` where the signature
11
- * is an HMAC-SHA256 of the session ID using the API key as secret.
12
- *
13
- * When `options.secure` is true the cookie includes the `Secure` flag,
14
- * which tells browsers to only send it over HTTPS. This should be
15
- * enabled when the server is network-accessible (`--allow-network`)
16
- * behind a TLS-terminating reverse proxy.
17
- */
18
- export declare function createSession(apiKey: string, options?: {
19
- secure?: boolean;
20
- }): string;
21
- /**
22
- * Parse a raw Cookie header into key-value pairs.
23
- *
24
- * Handles the standard `name=value; name2=value2` format.
25
- */
26
- export declare function parseCookies(header: string): Record<string, string>;
27
- /**
28
- * Validate a session cookie from a raw Cookie header string.
29
- *
30
- * Returns true if the cookie contains a valid, non-expired session
31
- * with a correct HMAC signature.
32
- */
33
- export declare function validateSessionCookie(cookieHeader: string, apiKey: string): boolean;
34
- /** Remove all sessions (useful for testing). */
35
- export declare function clearSessions(): void;
36
- //# sourceMappingURL=session.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,EAAE,MAA0B,CAAC;AAqB7D,6EAA6E;AAC7E,wBAAgB,mBAAmB,IAAI,IAAI,CAc1C;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAwBpF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAenE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAwCnF;AAED,gDAAgD;AAChD,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
package/dist/session.js DELETED
@@ -1,143 +0,0 @@
1
- import { randomBytes, createHmac } from "node:crypto";
2
- /** Name of the session cookie sent to browsers. */
3
- export const SESSION_COOKIE_NAME = "grackle_session";
4
- /** Default session lifetime: 24 hours. */
5
- const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
6
- /** Byte length of the random session identifier. */
7
- const SESSION_ID_BYTES = 32;
8
- /** Interval at which expired sessions are cleaned up. */
9
- const SESSION_CLEANUP_INTERVAL_MS = 60 * 1000;
10
- /** In-memory session store keyed by session ID. */
11
- const sessions = new Map();
12
- let cleanupTimer;
13
- /** Start the periodic session cleanup timer. Call once on server startup. */
14
- export function startSessionCleanup() {
15
- if (cleanupTimer) {
16
- return;
17
- }
18
- cleanupTimer = setInterval(() => {
19
- const now = Date.now();
20
- for (const [id, record] of sessions) {
21
- if (now > record.expiresAt) {
22
- sessions.delete(id);
23
- }
24
- }
25
- }, SESSION_CLEANUP_INTERVAL_MS);
26
- // Allow the process to exit even if the timer is still running
27
- cleanupTimer.unref();
28
- }
29
- /** Stop the periodic session cleanup timer. */
30
- export function stopSessionCleanup() {
31
- if (cleanupTimer) {
32
- clearInterval(cleanupTimer);
33
- cleanupTimer = undefined;
34
- }
35
- }
36
- /**
37
- * Create an HMAC-SHA256 signature of a value using the given secret.
38
- * Returns a hex-encoded string.
39
- */
40
- function sign(value, secret) {
41
- return createHmac("sha256", secret).update(value).digest("hex");
42
- }
43
- /**
44
- * Create a new session and return the Set-Cookie header value.
45
- *
46
- * The cookie format is `<sessionId>.<signature>` where the signature
47
- * is an HMAC-SHA256 of the session ID using the API key as secret.
48
- *
49
- * When `options.secure` is true the cookie includes the `Secure` flag,
50
- * which tells browsers to only send it over HTTPS. This should be
51
- * enabled when the server is network-accessible (`--allow-network`)
52
- * behind a TLS-terminating reverse proxy.
53
- */
54
- export function createSession(apiKey, options) {
55
- const sessionId = randomBytes(SESSION_ID_BYTES).toString("hex");
56
- const now = Date.now();
57
- sessions.set(sessionId, {
58
- createdAt: now,
59
- expiresAt: now + SESSION_TTL_MS,
60
- });
61
- const signature = sign(sessionId, apiKey);
62
- const cookieValue = `${sessionId}.${signature}`;
63
- const maxAge = Math.floor(SESSION_TTL_MS / 1000);
64
- const parts = [
65
- `${SESSION_COOKIE_NAME}=${cookieValue}`,
66
- "HttpOnly",
67
- "SameSite=Lax",
68
- "Path=/",
69
- `Max-Age=${maxAge}`,
70
- ];
71
- if (options?.secure) {
72
- parts.push("Secure");
73
- }
74
- return parts.join("; ");
75
- }
76
- /**
77
- * Parse a raw Cookie header into key-value pairs.
78
- *
79
- * Handles the standard `name=value; name2=value2` format.
80
- */
81
- export function parseCookies(header) {
82
- const result = {};
83
- if (!header) {
84
- return result;
85
- }
86
- for (const pair of header.split(";")) {
87
- const eqIndex = pair.indexOf("=");
88
- if (eqIndex === -1) {
89
- continue;
90
- }
91
- const name = pair.slice(0, eqIndex).trim();
92
- const value = pair.slice(eqIndex + 1).trim();
93
- result[name] = value;
94
- }
95
- return result;
96
- }
97
- /**
98
- * Validate a session cookie from a raw Cookie header string.
99
- *
100
- * Returns true if the cookie contains a valid, non-expired session
101
- * with a correct HMAC signature.
102
- */
103
- export function validateSessionCookie(cookieHeader, apiKey) {
104
- const cookies = parseCookies(cookieHeader);
105
- const raw = cookies[SESSION_COOKIE_NAME];
106
- if (!raw) {
107
- return false;
108
- }
109
- const dotIndex = raw.lastIndexOf(".");
110
- if (dotIndex === -1) {
111
- return false;
112
- }
113
- const sessionId = raw.slice(0, dotIndex);
114
- const providedSignature = raw.slice(dotIndex + 1);
115
- const expectedSignature = sign(sessionId, apiKey);
116
- // Constant-time comparison for the signature
117
- if (providedSignature.length !== expectedSignature.length) {
118
- return false;
119
- }
120
- let diff = 0;
121
- for (let i = 0; i < expectedSignature.length; i++) {
122
- // eslint-disable-next-line no-bitwise
123
- diff |= providedSignature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);
124
- }
125
- if (diff !== 0) {
126
- return false;
127
- }
128
- // Check session exists and hasn't expired
129
- const record = sessions.get(sessionId);
130
- if (!record) {
131
- return false;
132
- }
133
- if (Date.now() > record.expiresAt) {
134
- sessions.delete(sessionId);
135
- return false;
136
- }
137
- return true;
138
- }
139
- /** Remove all sessions (useful for testing). */
140
- export function clearSessions() {
141
- sessions.clear();
142
- }
143
- //# sourceMappingURL=session.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}