@mondaydotcomorg/atp-protocol 0.17.14

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.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Input validation utilities for ExecutionConfig and other types
3
+ */
4
+ import { z } from 'zod';
5
+ /**
6
+ * Maximum allowed code size (1MB)
7
+ */
8
+ export const MAX_CODE_SIZE = 1000000;
9
+ export class ConfigValidationError extends Error {
10
+ field;
11
+ value;
12
+ constructor(message, field, value) {
13
+ super(message);
14
+ this.field = field;
15
+ this.value = value;
16
+ this.name = 'ConfigValidationError';
17
+ }
18
+ }
19
+ export class SecurityViolationError extends Error {
20
+ violations;
21
+ constructor(message, violations) {
22
+ super(message);
23
+ this.violations = violations;
24
+ this.name = 'SecurityViolationError';
25
+ }
26
+ }
27
+ /**
28
+ * Sanitizes input string by removing control characters and normalizing whitespace
29
+ */
30
+ export function sanitizeInput(input, maxLength = MAX_CODE_SIZE) {
31
+ if (typeof input !== 'string') {
32
+ return '';
33
+ }
34
+ let sanitized = input.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
35
+ sanitized = sanitized.replace(/[\u200B-\u200D\uFEFF]/g, '');
36
+ sanitized = sanitized.replace(/\n{10,}/g, '\n\n\n');
37
+ if (sanitized.length > maxLength) {
38
+ sanitized = sanitized.substring(0, maxLength);
39
+ }
40
+ return sanitized;
41
+ }
42
+ /**
43
+ * Frames user code in a secure execution context to prevent injection attacks
44
+ * Similar to SQL parameterized queries - treats user code as data within a safe boundary
45
+ */
46
+ export function frameCodeExecution(userCode) {
47
+ const cleaned = sanitizeInput(userCode);
48
+ return `
49
+ (async function __user_code_context__() {
50
+ "use strict";
51
+ ${cleaned}
52
+ })();
53
+ `.trim();
54
+ }
55
+ /**
56
+ * Zod schema for ExecutionConfig validation
57
+ */
58
+ export const executionConfigSchema = z.object({
59
+ timeout: z
60
+ .number({
61
+ invalid_type_error: 'timeout must be a number',
62
+ })
63
+ .positive('timeout must be positive')
64
+ .max(300000, 'timeout cannot exceed 300000ms (5 minutes)')
65
+ .optional(),
66
+ maxMemory: z
67
+ .number({
68
+ invalid_type_error: 'maxMemory must be a number',
69
+ })
70
+ .positive('maxMemory must be positive')
71
+ .max(512 * 1024 * 1024, 'maxMemory cannot exceed 512MB')
72
+ .optional(),
73
+ maxLLMCalls: z
74
+ .number({
75
+ invalid_type_error: 'maxLLMCalls must be a number',
76
+ })
77
+ .nonnegative('maxLLMCalls cannot be negative')
78
+ .max(1000, 'maxLLMCalls cannot exceed 1000')
79
+ .optional(),
80
+ allowedAPIs: z
81
+ .array(z.string().refine((val) => val.trim().length > 0, {
82
+ message: 'allowedAPIs must contain non-empty strings',
83
+ }))
84
+ .optional(),
85
+ allowLLMCalls: z
86
+ .boolean({
87
+ invalid_type_error: 'allowLLMCalls must be a boolean',
88
+ })
89
+ .optional(),
90
+ progressCallback: z.function().optional(),
91
+ customLLMHandler: z.function().optional(),
92
+ clientServices: z.any().optional(),
93
+ provenanceMode: z.any().optional(),
94
+ securityPolicies: z.array(z.any()).optional(),
95
+ provenanceHints: z.array(z.string()).optional(),
96
+ });
97
+ /**
98
+ * Validates ExecutionConfig parameters using Zod
99
+ */
100
+ export function validateExecutionConfig(config) {
101
+ try {
102
+ executionConfigSchema.parse(config);
103
+ }
104
+ catch (error) {
105
+ if (error instanceof z.ZodError) {
106
+ const errors = error.errors.map((err) => err.message);
107
+ throw new ConfigValidationError(`Invalid ExecutionConfig: ${errors.join(', ')}`, 'ExecutionConfig', config);
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+ /**
113
+ * Validates client ID format
114
+ */
115
+ export function validateClientId(clientId) {
116
+ if (typeof clientId !== 'string') {
117
+ throw new ConfigValidationError('clientId must be a string', 'clientId', clientId);
118
+ }
119
+ if (clientId.trim().length === 0) {
120
+ throw new ConfigValidationError('clientId cannot be empty', 'clientId', clientId);
121
+ }
122
+ if (clientId.length > 256) {
123
+ throw new ConfigValidationError('clientId cannot exceed 256 characters', 'clientId', clientId);
124
+ }
125
+ if (!/^[a-zA-Z0-9_-]+$/.test(clientId)) {
126
+ throw new ConfigValidationError('clientId can only contain alphanumeric characters, dashes, and underscores', 'clientId', clientId);
127
+ }
128
+ }
129
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@mondaydotcomorg/atp-protocol",
3
+ "version": "0.17.14",
4
+ "description": "Core protocol types and interfaces for Agent Tool Protocol",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "dev": "tsc -p tsconfig.json --watch",
21
+ "clean": "rm -rf dist *.tsbuildinfo",
22
+ "test": "vitest run",
23
+ "lint": "tsc --noEmit"
24
+ },
25
+ "keywords": [
26
+ "agent",
27
+ "protocol",
28
+ "types",
29
+ "ai",
30
+ "llm"
31
+ ],
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@mondaydotcomorg/atp-provenance": "0.17.14",
35
+ "zod": "^3.23.8"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.3.3",
39
+ "vitest": "^1.2.1"
40
+ }
41
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Authentication and credential management types for Agent Tool Protocol
3
+ */
4
+
5
+ /**
6
+ * Supported authentication schemes
7
+ */
8
+ export type AuthScheme = 'apiKey' | 'bearer' | 'basic' | 'oauth2' | 'custom' | 'composite';
9
+
10
+ /**
11
+ * Base authentication configuration
12
+ */
13
+ export interface BaseAuthConfig {
14
+ scheme: AuthScheme;
15
+ /** Environment variable name to read credentials from */
16
+ envVar?: string;
17
+ /** Direct credential value (not recommended for production) */
18
+ value?: string;
19
+ /**
20
+ * Credential source: 'server' for server-level env vars (default), 'user' for user-scoped OAuth
21
+ */
22
+ source?: 'server' | 'user';
23
+ /**
24
+ * OAuth provider name for user-scoped credentials (e.g., 'github', 'google')
25
+ * Required when source='user'. Used to look up user's OAuth token from AuthProvider.
26
+ * Note: This is different from the 'provider' field which is for runtime credential providers.
27
+ */
28
+ oauthProvider?: string;
29
+ /** Runtime credential provider function name */
30
+ provider?: string;
31
+ }
32
+
33
+ /**
34
+ * API Key authentication (in header or query param)
35
+ */
36
+ export interface APIKeyAuthConfig extends BaseAuthConfig {
37
+ scheme: 'apiKey';
38
+ /** Where to send the API key */
39
+ in: 'header' | 'query';
40
+ /** Parameter/header name */
41
+ name: string;
42
+ }
43
+
44
+ /**
45
+ * Bearer token authentication
46
+ */
47
+ export interface BearerAuthConfig extends BaseAuthConfig {
48
+ scheme: 'bearer';
49
+ /** Optional bearer format (e.g., 'JWT') */
50
+ bearerFormat?: string;
51
+ }
52
+
53
+ /**
54
+ * HTTP Basic authentication
55
+ */
56
+ export interface BasicAuthConfig extends BaseAuthConfig {
57
+ scheme: 'basic';
58
+ /** Username (can use envVar for dynamic value) */
59
+ username?: string;
60
+ /** Username environment variable */
61
+ usernameEnvVar?: string;
62
+ /** Password environment variable */
63
+ passwordEnvVar?: string;
64
+ }
65
+
66
+ /**
67
+ * OAuth2 authentication with automatic token refresh
68
+ */
69
+ export interface OAuth2AuthConfig extends BaseAuthConfig {
70
+ scheme: 'oauth2';
71
+ /** OAuth2 flow type */
72
+ flow: 'clientCredentials' | 'authorizationCode' | 'implicit' | 'password';
73
+ /** Token endpoint URL */
74
+ tokenUrl: string;
75
+ /** Authorization endpoint (for authorizationCode/implicit) */
76
+ authorizationUrl?: string;
77
+ /** Client ID */
78
+ clientId?: string;
79
+ /** Client ID environment variable */
80
+ clientIdEnvVar?: string;
81
+ /** Client secret environment variable */
82
+ clientSecretEnvVar?: string;
83
+ /** Scopes required */
84
+ scopes?: string[];
85
+ /** Refresh token environment variable (for token refresh) */
86
+ refreshTokenEnvVar?: string;
87
+ }
88
+
89
+ /**
90
+ * Custom authentication with arbitrary headers
91
+ */
92
+ export interface CustomAuthConfig extends BaseAuthConfig {
93
+ scheme: 'custom';
94
+ /** Custom headers to inject */
95
+ headers: Record<string, string>;
96
+ /** Environment variables to use for header values */
97
+ headerEnvVars?: Record<string, string>;
98
+ /** Query parameters to inject */
99
+ queryParams?: Record<string, string>;
100
+ /** Environment variables to use for query parameter values */
101
+ queryParamEnvVars?: Record<string, string>;
102
+ }
103
+
104
+ /**
105
+ * Composite authentication - combines multiple auth mechanisms
106
+ * Useful for APIs that require multiple credentials (e.g., projectId + apiKey + secret)
107
+ */
108
+ export interface CompositeAuthConfig extends BaseAuthConfig {
109
+ scheme: 'composite';
110
+ /**
111
+ * Multiple credentials to combine
112
+ * Example: { projectId: { envVar: 'PROJECT_ID' }, apiKey: { envVar: 'API_KEY' }, secret: { envVar: 'API_SECRET' } }
113
+ */
114
+ credentials: Record<string, CredentialConfig>;
115
+ /** How to inject credentials: 'header', 'query', or 'both' */
116
+ injectAs?: 'header' | 'query' | 'both';
117
+ }
118
+
119
+ /**
120
+ * Individual credential configuration for composite auth
121
+ */
122
+ export interface CredentialConfig {
123
+ /** Environment variable to read from */
124
+ envVar?: string;
125
+ /** Direct value (not recommended) */
126
+ value?: string;
127
+ /** Header name if injecting as header */
128
+ headerName?: string;
129
+ /** Query param name if injecting as query */
130
+ queryParamName?: string;
131
+ /** Whether this credential is required */
132
+ required?: boolean;
133
+ }
134
+
135
+ /**
136
+ * Union type of all auth configurations
137
+ */
138
+ export type AuthConfig =
139
+ | APIKeyAuthConfig
140
+ | BearerAuthConfig
141
+ | BasicAuthConfig
142
+ | OAuth2AuthConfig
143
+ | CustomAuthConfig
144
+ | CompositeAuthConfig;
145
+
146
+ /**
147
+ * Runtime credential provider
148
+ * Allows dynamic credential resolution at runtime
149
+ */
150
+ export interface CredentialProvider {
151
+ name: string;
152
+ /** Resolves credentials dynamically */
153
+ resolve: () => Promise<Credentials> | Credentials;
154
+ }
155
+
156
+ /**
157
+ * Resolved credentials ready to be injected into requests
158
+ */
159
+ export interface Credentials {
160
+ headers?: Record<string, string>;
161
+ queryParams?: Record<string, string>;
162
+ }
163
+
164
+ /**
165
+ * Credential resolver - resolves auth config to actual credentials
166
+ */
167
+ export class CredentialResolver {
168
+ private providers: Map<string, CredentialProvider> = new Map();
169
+
170
+ /**
171
+ * Registers a runtime credential provider
172
+ */
173
+ registerProvider(provider: CredentialProvider): void {
174
+ this.providers.set(provider.name, provider);
175
+ }
176
+
177
+ /**
178
+ * Resolves auth configuration to credentials
179
+ */
180
+ async resolve(authConfig: AuthConfig): Promise<Credentials> {
181
+ if (authConfig.provider) {
182
+ const provider = this.providers.get(authConfig.provider);
183
+ if (!provider) {
184
+ throw new Error(`Credential provider '${authConfig.provider}' not found`);
185
+ }
186
+ return await provider.resolve();
187
+ }
188
+
189
+ switch (authConfig.scheme) {
190
+ case 'apiKey':
191
+ return this.resolveAPIKey(authConfig);
192
+ case 'bearer':
193
+ return this.resolveBearer(authConfig);
194
+ case 'basic':
195
+ return this.resolveBasic(authConfig);
196
+ case 'oauth2':
197
+ return this.resolveOAuth2(authConfig);
198
+ case 'custom':
199
+ return this.resolveCustom(authConfig);
200
+ case 'composite':
201
+ return this.resolveComposite(authConfig);
202
+ default:
203
+ throw new Error(`Unsupported auth scheme: ${(authConfig as any).scheme}`);
204
+ }
205
+ }
206
+
207
+ private resolveAPIKey(config: APIKeyAuthConfig): Credentials {
208
+ const value = this.getValue(config);
209
+ if (!value) {
210
+ throw new Error(`API key not provided for '${config.name}'`);
211
+ }
212
+
213
+ if (config.in === 'header') {
214
+ return { headers: { [config.name]: value } };
215
+ } else {
216
+ return { queryParams: { [config.name]: value } };
217
+ }
218
+ }
219
+
220
+ private resolveBearer(config: BearerAuthConfig): Credentials {
221
+ const token = this.getValue(config);
222
+ if (!token) {
223
+ throw new Error('Bearer token not provided');
224
+ }
225
+
226
+ return {
227
+ headers: {
228
+ Authorization: `Bearer ${token}`,
229
+ },
230
+ };
231
+ }
232
+
233
+ private resolveBasic(config: BasicAuthConfig): Credentials {
234
+ const username = config.usernameEnvVar ? process.env[config.usernameEnvVar] : config.username;
235
+ const password = config.passwordEnvVar
236
+ ? process.env[config.passwordEnvVar]
237
+ : this.getValue(config);
238
+
239
+ if (!username || !password) {
240
+ throw new Error('Basic auth username and password not provided');
241
+ }
242
+
243
+ const credentials = Buffer.from(`${username}:${password}`).toString('base64');
244
+ return {
245
+ headers: {
246
+ Authorization: `Basic ${credentials}`,
247
+ },
248
+ };
249
+ }
250
+
251
+ private async resolveOAuth2(config: OAuth2AuthConfig): Promise<Credentials> {
252
+ const clientId = config.clientIdEnvVar ? process.env[config.clientIdEnvVar] : config.clientId;
253
+ const clientSecret = config.clientSecretEnvVar
254
+ ? process.env[config.clientSecretEnvVar]
255
+ : undefined;
256
+
257
+ if (!clientId || !clientSecret) {
258
+ throw new Error('OAuth2 client credentials not provided');
259
+ }
260
+
261
+ if (config.flow === 'clientCredentials') {
262
+ const token = await this.fetchOAuth2Token(
263
+ config.tokenUrl,
264
+ clientId,
265
+ clientSecret,
266
+ config.scopes
267
+ );
268
+ return {
269
+ headers: {
270
+ Authorization: `Bearer ${token}`,
271
+ },
272
+ };
273
+ }
274
+
275
+ const token = this.getValue(config);
276
+ if (token) {
277
+ return {
278
+ headers: {
279
+ Authorization: `Bearer ${token}`,
280
+ },
281
+ };
282
+ }
283
+
284
+ throw new Error(`OAuth2 flow '${config.flow}' requires manual token setup`);
285
+ }
286
+
287
+ private resolveCustom(config: CustomAuthConfig): Credentials {
288
+ const headers: Record<string, string> = {};
289
+ const queryParams: Record<string, string> = {};
290
+
291
+ Object.assign(headers, config.headers);
292
+
293
+ if (config.headerEnvVars) {
294
+ for (const [headerName, envVar] of Object.entries(config.headerEnvVars)) {
295
+ const value = process.env[envVar];
296
+ if (value) {
297
+ headers[headerName] = value;
298
+ }
299
+ }
300
+ }
301
+
302
+ if (config.queryParams) {
303
+ Object.assign(queryParams, config.queryParams);
304
+ }
305
+
306
+ if (config.queryParamEnvVars) {
307
+ for (const [paramName, envVar] of Object.entries(config.queryParamEnvVars)) {
308
+ const value = process.env[envVar];
309
+ if (value) {
310
+ queryParams[paramName] = value;
311
+ }
312
+ }
313
+ }
314
+
315
+ return {
316
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
317
+ queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
318
+ };
319
+ }
320
+
321
+ private resolveComposite(config: CompositeAuthConfig): Credentials {
322
+ const headers: Record<string, string> = {};
323
+ const queryParams: Record<string, string> = {};
324
+
325
+ for (const [credName, credConfig] of Object.entries(config.credentials)) {
326
+ const value = credConfig.envVar ? process.env[credConfig.envVar] : credConfig.value;
327
+
328
+ if (!value) {
329
+ if (credConfig.required !== false) {
330
+ throw new Error(`Required credential '${credName}' not provided`);
331
+ }
332
+ continue;
333
+ }
334
+
335
+ const injectAs = config.injectAs || 'header';
336
+
337
+ if ((injectAs === 'header' || injectAs === 'both') && credConfig.headerName) {
338
+ headers[credConfig.headerName] = value;
339
+ }
340
+
341
+ if ((injectAs === 'query' || injectAs === 'both') && credConfig.queryParamName) {
342
+ queryParams[credConfig.queryParamName] = value;
343
+ }
344
+
345
+ if (!credConfig.headerName && !credConfig.queryParamName) {
346
+ if (injectAs === 'query' || injectAs === 'both') {
347
+ queryParams[credName] = value;
348
+ } else {
349
+ headers[`X-${credName}`] = value;
350
+ }
351
+ }
352
+ }
353
+
354
+ return {
355
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
356
+ queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Gets credential value from config (env var or direct value)
362
+ */
363
+ private getValue(config: BaseAuthConfig): string | undefined {
364
+ if (config.envVar) {
365
+ return process.env[config.envVar];
366
+ }
367
+ return config.value;
368
+ }
369
+
370
+ /**
371
+ * Fetches OAuth2 token using client credentials flow
372
+ */
373
+ private async fetchOAuth2Token(
374
+ tokenUrl: string,
375
+ clientId: string,
376
+ clientSecret: string,
377
+ scopes?: string[]
378
+ ): Promise<string> {
379
+ const params = new URLSearchParams({
380
+ grant_type: 'client_credentials',
381
+ client_id: clientId,
382
+ client_secret: clientSecret,
383
+ });
384
+
385
+ if (scopes && scopes.length > 0) {
386
+ params.append('scope', scopes.join(' '));
387
+ }
388
+
389
+ const response = await fetch(tokenUrl, {
390
+ method: 'POST',
391
+ headers: {
392
+ 'Content-Type': 'application/x-www-form-urlencoded',
393
+ },
394
+ body: params.toString(),
395
+ });
396
+
397
+ if (!response.ok) {
398
+ throw new Error(`OAuth2 token fetch failed: ${response.statusText}`);
399
+ }
400
+
401
+ const data = (await response.json()) as { access_token: string };
402
+ return data.access_token;
403
+ }
404
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export * from './schemas.js';
3
+ export * from './validation.js';
4
+ export * from './auth.js';
5
+ export * from './providers.js';
6
+ export * from './oauth.js';
package/src/oauth.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * OAuth and scope checking interfaces for Agent Tool Protocol
3
+ */
4
+
5
+ /**
6
+ * Scope checker interface
7
+ * Checks what OAuth scopes a token has for a given provider
8
+ */
9
+ export interface ScopeChecker {
10
+ /** Provider name (e.g., 'github', 'google', 'microsoft') */
11
+ provider: string;
12
+
13
+ /**
14
+ * Check what scopes a token has
15
+ * @param token - Access token to check
16
+ * @returns Array of scope strings (e.g., ['repo', 'read:user'])
17
+ */
18
+ check(token: string): Promise<string[]>;
19
+
20
+ /**
21
+ * Validate if a token is still valid (optional)
22
+ * @param token - Access token to validate
23
+ * @returns true if valid, false if expired/revoked
24
+ */
25
+ validate?(token: string): Promise<boolean>;
26
+ }
27
+
28
+ /**
29
+ * Token information returned by providers
30
+ */
31
+ export interface TokenInfo {
32
+ /** Whether the token is valid */
33
+ valid: boolean;
34
+
35
+ /** OAuth scopes the token has */
36
+ scopes: string[];
37
+
38
+ /** Token expiration timestamp (milliseconds since epoch) */
39
+ expiresAt?: number;
40
+
41
+ /** User identifier from the provider */
42
+ userId?: string;
43
+
44
+ /** Additional provider-specific data */
45
+ metadata?: Record<string, unknown>;
46
+ }
47
+
48
+ /**
49
+ * Scope filtering configuration
50
+ */
51
+ export interface ScopeFilteringConfig {
52
+ /** Enable scope-based filtering */
53
+ enabled: boolean;
54
+
55
+ /**
56
+ * Filtering mode:
57
+ * - 'eager': Filter tools at /api/definitions based on user's scopes
58
+ * - 'lazy': Return all tools, validate scopes only at execution time
59
+ */
60
+ mode: 'eager' | 'lazy';
61
+
62
+ /**
63
+ * Cache TTL for scope checks in seconds
64
+ * Default: 3600 (1 hour)
65
+ */
66
+ cacheTTL?: number;
67
+
68
+ /**
69
+ * Fail behavior when scope checker not available for a provider
70
+ * - 'allow': Allow all tools (no filtering)
71
+ * - 'deny': Hide all tools requiring scopes
72
+ */
73
+ fallback?: 'allow' | 'deny';
74
+ }