@xano/developer-mcp 1.0.57 → 1.0.59

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 (38) hide show
  1. package/README.md +15 -0
  2. package/dist/tools/index.d.ts +9 -0
  3. package/dist/tools/xanoscript_docs.d.ts +9 -0
  4. package/dist/tools/xanoscript_docs.js +27 -0
  5. package/dist/xanoscript.d.ts +5 -1
  6. package/dist/xanoscript.js +70 -6
  7. package/dist/xanoscript.test.js +5 -3
  8. package/dist/xanoscript_docs/README.md +9 -43
  9. package/dist/xanoscript_docs/addons.md +0 -2
  10. package/dist/xanoscript_docs/agents.md +2 -35
  11. package/dist/xanoscript_docs/apis.md +3 -6
  12. package/dist/xanoscript_docs/branch.md +0 -2
  13. package/dist/xanoscript_docs/database.md +3 -7
  14. package/dist/xanoscript_docs/debugging.md +1 -264
  15. package/dist/xanoscript_docs/docs_index.json +22 -0
  16. package/dist/xanoscript_docs/essentials.md +1 -9
  17. package/dist/xanoscript_docs/frontend.md +1 -138
  18. package/dist/xanoscript_docs/functions.md +3 -7
  19. package/dist/xanoscript_docs/mcp-servers.md +1 -2
  20. package/dist/xanoscript_docs/middleware.md +1 -3
  21. package/dist/xanoscript_docs/performance.md +8 -198
  22. package/dist/xanoscript_docs/realtime.md +11 -161
  23. package/dist/xanoscript_docs/run.md +2 -184
  24. package/dist/xanoscript_docs/schema.md +1 -3
  25. package/dist/xanoscript_docs/security.md +82 -313
  26. package/dist/xanoscript_docs/streaming.md +2 -37
  27. package/dist/xanoscript_docs/survival.md +161 -0
  28. package/dist/xanoscript_docs/syntax.md +0 -6
  29. package/dist/xanoscript_docs/tables.md +3 -5
  30. package/dist/xanoscript_docs/tasks.md +1 -3
  31. package/dist/xanoscript_docs/tools.md +1 -3
  32. package/dist/xanoscript_docs/triggers.md +3 -69
  33. package/dist/xanoscript_docs/types.md +3 -4
  34. package/dist/xanoscript_docs/unit-testing.md +1 -55
  35. package/dist/xanoscript_docs/workflow-tests.md +8 -35
  36. package/dist/xanoscript_docs/working.md +667 -0
  37. package/dist/xanoscript_docs/workspace.md +0 -2
  38. package/package.json +2 -2
@@ -8,19 +8,6 @@ Best practices for building secure XanoScript applications.
8
8
 
9
9
  > **TL;DR:** Always check `$auth.id` for protected endpoints. Use `security.create_auth_token` for JWT. Validate all inputs with `filters=`. Hash passwords with `password` type. Use `$env.SECRET_NAME` for secrets.
10
10
 
11
- ## Section Index
12
-
13
- - [Authentication](#authentication) - Tokens, sessions, refresh tokens
14
- - [Authorization](#authorization) - Role checks, ownership
15
- - [Input Validation](#input-validation) - Type enforcement, sanitization
16
- - [Data Protection](#data-protection) - Encryption, hashing, secrets
17
- - [Rate Limiting](#rate-limiting--abuse-prevention) - API limits, abuse prevention
18
- - [Security Headers](#security-headers) - CORS configuration
19
- - [Audit Logging](#audit-logging) - Security event tracking
20
- - [Best Practices Summary](#best-practices-summary) - Quick checklist
21
-
22
- ---
23
-
24
11
  ## Quick Reference
25
12
 
26
13
  | Area | Key Practices |
@@ -62,104 +49,46 @@ precondition ($auth.id != null) {
62
49
 
63
50
  ### Refresh Tokens
64
51
 
52
+ Pattern: Store refresh tokens in a `refresh_token` table. On refresh, verify the token hasn't expired or been revoked, generate a new access token via `security.create_auth_token`, rotate the refresh token with `security.create_uuid`, and update the stored token.
53
+
65
54
  ```xs
66
- function "refresh_auth" {
67
- input {
68
- text refresh_token
69
- }
70
- stack {
71
- // Verify refresh token
72
- db.query "refresh_token" {
73
- where = $db.refresh_token.token == $input.refresh_token
74
- && $db.refresh_token.expires_at > now
75
- && $db.refresh_token.revoked == false
76
- return = { type: "single" }
77
- } as $stored_token
78
-
79
- precondition ($stored_token != null) {
80
- error_type = "accessdenied"
81
- error = "Invalid or expired refresh token"
82
- }
83
-
84
- // Get user
85
- db.get "user" {
86
- field_name = "id"
87
- field_value = $stored_token.user_id
88
- } as $user
89
-
90
- // Generate new access token
91
- security.create_auth_token {
92
- table = "user"
93
- id = $user.id
94
- extras = { role: $user.role }
95
- expiration = 3600
96
- } as $new_token
97
-
98
- // Rotate refresh token
99
- security.create_uuid as $new_refresh
100
-
101
- db.edit "refresh_token" {
102
- field_name = "id"
103
- field_value = $stored_token.id
104
- data = {
105
- token: $new_refresh,
106
- expires_at: now|transform_timestamp:"+30 days"
107
- }
108
- }
109
- }
110
- response = {
111
- access_token: $new_token,
112
- refresh_token: $new_refresh
113
- }
114
- }
55
+ // Key operations in a refresh flow:
56
+ db.query "refresh_token" {
57
+ where = $db.refresh_token.token == $input.refresh_token
58
+ && $db.refresh_token.expires_at > now
59
+ && $db.refresh_token.revoked == false
60
+ return = { type: "single" }
61
+ } as $stored_token
62
+
63
+ security.create_auth_token {
64
+ table = "user"
65
+ id = $user.id
66
+ extras = { role: $user.role }
67
+ expiration = 3600
68
+ } as $new_token
115
69
  ```
116
70
 
117
71
  ### Session Management
118
72
 
73
+ Pattern: Use `security.create_uuid` for session IDs. Store in a `session` table with `user_id`, `ip_address` (`$env.$remote_ip`), `expires_at`, and `last_activity`. Validate by querying where `expires_at > now`.
74
+
119
75
  ```xs
120
- function "create_session" {
121
- input { int user_id }
122
- stack {
123
- security.create_uuid as $session_id
124
-
125
- db.add "session" {
126
- data = {
127
- id: $session_id,
128
- user_id: $input.user_id,
129
- ip_address: $env.$remote_ip,
130
- user_agent: $env.$http_headers|get:"user-agent",
131
- created_at: now,
132
- last_activity: now,
133
- expires_at: now|transform_timestamp:"+7 days"
134
- }
135
- }
76
+ // Create session
77
+ security.create_uuid as $session_id
78
+ db.add "session" {
79
+ data = {
80
+ id: $session_id,
81
+ user_id: $input.user_id,
82
+ expires_at: now|transform_timestamp:"+7 days"
136
83
  }
137
- response = $session_id
138
84
  }
139
85
 
140
- function "validate_session" {
141
- input { text session_id }
142
- stack {
143
- db.query "session" {
144
- where = $db.session.id == $input.session_id
145
- && $db.session.expires_at > now
146
- return = { type: "single" }
147
- } as $session
148
-
149
- precondition ($session != null) {
150
- error_type = "accessdenied"
151
- error = "Invalid or expired session"
152
- }
153
-
154
- // Update last activity
155
- db.edit "session" {
156
- field_name = "id"
157
- field_value = $session.id
158
- data = { last_activity: now }
159
- }
160
- }
161
- response = $session
162
- }
86
+ // Validate session
87
+ db.query "session" {
88
+ where = $db.session.id == $input.session_id
89
+ && $db.session.expires_at > now
90
+ return = { type: "single" }
91
+ } as $session
163
92
  ```
164
93
 
165
94
  ---
@@ -178,25 +107,9 @@ precondition ($auth.role == "admin") {
178
107
  ### Permission Checks
179
108
 
180
109
  ```xs
181
- function "check_permission" {
182
- input {
183
- text permission
184
- }
185
- stack {
186
- var $has_permission {
187
- value = $auth.permissions|contains:$input.permission
188
- }
189
-
190
- precondition ($has_permission) {
191
- error_type = "accessdenied"
192
- error = "Missing permission: " ~ $input.permission
193
- }
194
- }
195
- }
196
-
197
- // Usage
198
- function.run "check_permission" {
199
- input = { permission: "users.delete" }
110
+ precondition ($auth.permissions|contains:"users.delete") {
111
+ error_type = "accessdenied"
112
+ error = "Missing permission: users.delete"
200
113
  }
201
114
  ```
202
115
 
@@ -217,40 +130,19 @@ precondition ($document.owner_id == $auth.id) {
217
130
 
218
131
  ### Hierarchical Access
219
132
 
133
+ Check ownership first, then team membership via `join`. Combine with action-specific logic:
134
+
220
135
  ```xs
221
- function "can_access_resource" {
222
- input {
223
- int resource_id
224
- text action
225
- }
226
- stack {
227
- // Check direct ownership
228
- db.query "resource" {
229
- where = $db.resource.id == $input.resource_id
230
- && $db.resource.owner_id == $auth.id
231
- return = { type: "exists" }
232
- } as $is_owner
233
-
234
- // Check team membership
235
- db.query "team_member" {
236
- join = {
237
- resource: {
238
- table: "resource",
239
- where: $db.resource.team_id == $db.team_member.team_id
240
- }
241
- }
242
- where = $db.resource.id == $input.resource_id
243
- && $db.team_member.user_id == $auth.id
244
- return = { type: "exists" }
245
- } as $is_team_member
246
-
247
- // Check permissions
248
- var $can_access {
249
- value = $is_owner || ($is_team_member && $input.action != "delete")
250
- }
251
- }
252
- response = $can_access
253
- }
136
+ db.query "resource" {
137
+ where = $db.resource.id == $input.resource_id && $db.resource.owner_id == $auth.id
138
+ return = { type: "exists" }
139
+ } as $is_owner
140
+
141
+ db.query "team_member" {
142
+ join = { resource: { table: "resource", where: $db.resource.team_id == $db.team_member.team_id } }
143
+ where = $db.resource.id == $input.resource_id && $db.team_member.user_id == $auth.id
144
+ return = { type: "exists" }
145
+ } as $is_team_member
254
146
  ```
255
147
 
256
148
  ---
@@ -286,33 +178,15 @@ db.direct_query {
286
178
  // sql = "SELECT * FROM users WHERE email = '" ~ $input.email ~ "'"
287
179
  ```
288
180
 
289
- ### XSS Prevention
181
+ ### XSS / Path Traversal Prevention
290
182
 
291
183
  ```xs
292
- // Escape HTML content before storage or display
293
- var $safe_content {
294
- value = $input.content|escape
295
- }
296
-
297
- // For rich text, escape HTML entities
298
- var $sanitized {
299
- value = $input.html|escape
300
- }
301
- ```
302
-
303
- ### Path Traversal Prevention
184
+ var $safe_content { value = $input.content|escape } // Escape HTML
304
185
 
305
- ```xs
306
- // Validate file paths
307
- precondition (!($input.filename|contains:"..")) {
186
+ precondition (!($input.filename|contains:"..")) { // Validate paths
308
187
  error_type = "inputerror"
309
188
  error = "Invalid filename"
310
189
  }
311
-
312
- precondition ($input.filename|regex_matches:"/^[a-zA-Z0-9_.-]+$/") {
313
- error_type = "inputerror"
314
- error = "Filename contains invalid characters"
315
- }
316
190
  ```
317
191
 
318
192
  ---
@@ -337,104 +211,49 @@ security.check_password {
337
211
  } as $is_valid
338
212
  ```
339
213
 
340
- ### Encryption at Rest
214
+ ### Encryption
341
215
 
342
216
  ```xs
343
- // Encrypt sensitive data before storage
217
+ // Encrypt/decrypt sensitive data
344
218
  security.encrypt {
345
219
  data = $input.ssn
346
220
  algorithm = "aes-256-cbc"
347
221
  key = $env.ENCRYPTION_KEY
348
222
  iv = $env.ENCRYPTION_IV
349
- } as $encrypted_ssn
350
-
351
- db.add "user" {
352
- data = { ssn_encrypted: $encrypted_ssn }
353
- }
223
+ } as $encrypted
354
224
 
355
- // Decrypt when needed
356
225
  security.decrypt {
357
- data = $user.ssn_encrypted
226
+ data = $encrypted
358
227
  algorithm = "aes-256-cbc"
359
228
  key = $env.ENCRYPTION_KEY
360
229
  iv = $env.ENCRYPTION_IV
361
- } as $ssn
230
+ } as $decrypted
362
231
  ```
363
232
 
364
233
  ### Secrets Management
365
234
 
366
235
  ```xs
367
- // Store secrets in environment variables
236
+ // Use $env for secrets NEVER hardcode
368
237
  $env.API_SECRET_KEY
369
- $env.DATABASE_URL
370
238
  $env.ENCRYPTION_KEY
371
239
 
372
- // NEVER hardcode secrets
373
- // api_key = "sk_live_abc123" // BAD
374
-
375
240
  // Mark sensitive inputs
376
241
  input {
377
- text api_key {
378
- sensitive = true // Masked in logs
379
- }
242
+ text api_key { sensitive = true } // Masked in logs
380
243
  }
381
244
  ```
382
245
 
383
- ### Key Generation
384
-
385
- ```xs
386
- // Generate elliptic curve key
387
- security.create_curve_key {
388
- curve = "P-256"
389
- format = "object"
390
- } as $crypto_key
391
-
392
- // Generate secret key for symmetric encryption
393
- security.create_secret_key {
394
- bits = 2048
395
- format = "object"
396
- } as $secret_key
397
- ```
398
-
399
- ### JWE (Encrypted JWT)
400
-
401
- ```xs
402
- // Encrypt JWT payload
403
- security.jwe_encode {
404
- headers = { "alg": "A256KW" }
405
- claims = { data: "secret" }
406
- key = $env.JWE_KEY
407
- key_algorithm = "A256KW"
408
- content_algorithm = "A256GCM"
409
- ttl = 3600
410
- } as $encrypted_token
411
-
412
- // Decrypt JWE token
413
- security.jwe_decode {
414
- token = $encrypted_token
415
- key = $env.JWE_KEY
416
- key_algorithm = "A256KW"
417
- content_algorithm = "A256GCM"
418
- check_claims = { "iss": "my_app" }
419
- } as $decoded_payload
420
- ```
421
-
422
- ### JWT Security
246
+ ### JWT (JWS/JWE)
423
247
 
424
248
  ```xs
425
- // Sign with strong algorithm
249
+ // Sign JWT
426
250
  security.jws_encode {
427
- claims = {
428
- sub: $user.id|to_text,
429
- role: $user.role,
430
- iat: now|to_seconds,
431
- exp: (now|to_seconds) + 3600
432
- }
251
+ claims = { sub: $user.id|to_text, role: $user.role }
433
252
  key = $env.JWT_SECRET
434
253
  signature_algorithm = "HS256"
435
254
  } as $token
436
255
 
437
- // Verify with algorithm check
256
+ // Verify JWT
438
257
  security.jws_decode {
439
258
  token = $input.token
440
259
  key = $env.JWT_SECRET
@@ -442,6 +261,8 @@ security.jws_decode {
442
261
  } as $claims
443
262
  ```
444
263
 
264
+ > See `xanoscript_docs({ topic: "integrations/utilities" })` for JWE, key generation, and other security utilities.
265
+
445
266
  ---
446
267
 
447
268
  ## Rate Limiting & Abuse Prevention
@@ -459,41 +280,19 @@ redis.ratelimit {
459
280
 
460
281
  ### Login Attempt Limiting
461
282
 
462
- ```xs
463
- function "check_login_attempts" {
464
- input { text email }
465
- stack {
466
- var $key { value = "login_attempts:" ~ $input.email|md5 }
467
-
468
- redis.get { key = $key } as $attempts
283
+ Use Redis to track failed attempts per email with a TTL-based lockout:
469
284
 
470
- precondition (($attempts|to_int ?? 0) < 5) {
471
- error_type = "accessdenied"
472
- error = "Too many login attempts. Try again in 15 minutes."
473
- }
474
- }
475
- }
285
+ ```xs
286
+ var $key { value = "login_attempts:" ~ $input.email|md5 }
287
+ redis.get { key = $key } as $attempts
476
288
 
477
- function "record_failed_login" {
478
- input { text email }
479
- stack {
480
- var $key { value = "login_attempts:" ~ $input.email|md5 }
481
-
482
- redis.get { key = $key } as $current
483
- redis.set {
484
- key = $key
485
- data = ($current|to_int ?? 0) + 1
486
- ttl = 900
487
- }
488
- }
289
+ precondition (($attempts|to_int ?? 0) < 5) {
290
+ error_type = "accessdenied"
291
+ error = "Too many login attempts. Try again in 15 minutes."
489
292
  }
490
293
 
491
- function "clear_login_attempts" {
492
- input { text email }
493
- stack {
494
- redis.del { key = "login_attempts:" ~ $input.email|md5 }
495
- }
496
- }
294
+ // On failure: redis.set { key = $key, data = ($attempts|to_int ?? 0) + 1, ttl = 900 }
295
+ // On success: redis.del { key = $key }
497
296
  ```
498
297
 
499
298
  ### Request Size Limits
@@ -528,39 +327,16 @@ precondition ($allowed_origins|contains:$env.$http_headers|get:"origin") {
528
327
 
529
328
  ## Audit Logging
530
329
 
531
- ### Log Security Events
330
+ Log security events to an `audit_log` table with `user_id` (`$auth.id`), `action`, `resource_type`, `ip_address` (`$env.$remote_ip`), and `created_at`.
532
331
 
533
332
  ```xs
534
- function "audit_log" {
535
- input {
536
- text action
537
- text resource_type
538
- int? resource_id
539
- json? details
540
- }
541
- stack {
542
- db.add "audit_log" {
543
- data = {
544
- user_id: $auth.id,
545
- action: $input.action,
546
- resource_type: $input.resource_type,
547
- resource_id: $input.resource_id,
548
- details: $input.details,
549
- ip_address: $env.$remote_ip,
550
- user_agent: $env.$http_headers|get:"user-agent",
551
- created_at: now
552
- }
553
- }
554
- }
555
- }
556
-
557
- // Usage
558
- function.run "audit_log" {
559
- input = {
560
- action: "delete",
561
- resource_type: "user",
562
- resource_id: $deleted_user.id,
563
- details: { reason: $input.reason }
333
+ db.add "audit_log" {
334
+ data = {
335
+ user_id: $auth.id,
336
+ action: $input.action,
337
+ resource_type: $input.resource_type,
338
+ ip_address: $env.$remote_ip,
339
+ created_at: now
564
340
  }
565
341
  }
566
342
  ```
@@ -569,16 +345,9 @@ function.run "audit_log" {
569
345
 
570
346
  ## Best Practices Summary
571
347
 
572
- 1. **Validate all inputs** - Use types and filters
573
- 2. **Check authorization** - Verify permissions for every operation
574
- 3. **Use parameterized queries** - Never concatenate user input into SQL
575
- 4. **Hash passwords** - Use built-in password type
576
- 5. **Encrypt sensitive data** - SSN, payment info, etc.
577
- 6. **Store secrets in env vars** - Never hardcode
578
- 7. **Rate limit APIs** - Prevent abuse
579
- 8. **Log security events** - Audit trail for compliance
580
- 9. **Use HTTPS** - Always (handled by platform)
581
- 10. **Rotate tokens** - Implement refresh token flow
348
+ 1. **Validate all inputs** - Use types and filters; never concatenate user input into SQL
349
+ 2. **Check authorization** - Verify `$auth.id` and permissions for every operation
350
+ 3. **Store secrets in env vars** - Never hardcode; use `$env.SECRET_NAME`
582
351
 
583
352
  ---
584
353
 
@@ -276,39 +276,6 @@ function "import_large_csv" {
276
276
  }
277
277
  ```
278
278
 
279
- ### ETL Pipeline
280
-
281
- ```xs
282
- function "etl_events" {
283
- input { file events_file }
284
- stack {
285
- stream.from_jsonl {
286
- value = $input.events_file
287
- } as $stream
288
-
289
- foreach ($stream) {
290
- each as $event {
291
- // Transform
292
- var $transformed {
293
- value = {
294
- event_type: $event.type|to_lower,
295
- user_id: $event.user_id|to_int,
296
- metadata: $event.data|json_encode,
297
- occurred_at: $event.timestamp|to_timestamp
298
- }
299
- }
300
-
301
- // Load
302
- db.add "processed_event" {
303
- data = $transformed
304
- }
305
- }
306
- }
307
- }
308
- response = { status: "complete" }
309
- }
310
- ```
311
-
312
279
  ### Chunked Export
313
280
 
314
281
  ```xs
@@ -334,10 +301,8 @@ query "stream_large_dataset" {
334
301
  ## Best Practices
335
302
 
336
303
  1. **Use streaming for large files** - Prevents memory exhaustion
337
- 2. **Process in batches** - Commit database operations periodically
338
- 3. **Handle errors gracefully** - Log failures without stopping stream
339
- 4. **Set appropriate chunk sizes** - Balance memory and performance
340
- 5. **Use JSONL for structured data** - Easier to parse than multi-line JSON
304
+ 2. **Handle errors gracefully** - Log failures without stopping stream
305
+ 3. **Use JSONL for structured data** - Easier to parse than multi-line JSON
341
306
 
342
307
  ---
343
308