@squidcloud/cli 1.0.445 → 1.0.447
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.js +3 -6
- package/dist/resources/claude/skills/squid-development/SKILL.md +66 -2473
- package/dist/resources/claude/skills/squid-development/reference/admin.md +242 -0
- package/dist/resources/claude/skills/squid-development/reference/ai.md +773 -0
- package/dist/resources/claude/skills/squid-development/reference/api.md +87 -0
- package/dist/resources/claude/skills/squid-development/reference/backend.md +462 -0
- package/dist/resources/claude/skills/squid-development/reference/client.md +442 -0
- package/dist/resources/claude/skills/squid-development/reference/connectors.md +33 -0
- package/dist/resources/claude/skills/squid-development/reference/console.md +189 -0
- package/dist/resources/claude/skills/squid-development/reference/databases.md +472 -0
- package/dist/resources/claude/skills/squid-development/reference/openai.md +98 -0
- package/dist/resources/claude/skills/squid-development/reference/security.md +356 -0
- package/package.json +2 -2
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Security Rules
|
|
2
|
+
|
|
3
|
+
Squid provides a comprehensive security model using backend decorators. Every operation can be secured using security rules - backend functions that contain business logic to determine whether an operation is allowed.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
- Overview
|
|
7
|
+
- Auth Methods
|
|
8
|
+
- Database Security
|
|
9
|
+
- Queue Security
|
|
10
|
+
- Storage Security
|
|
11
|
+
- API Security
|
|
12
|
+
- Native Query Security
|
|
13
|
+
- AI Security
|
|
14
|
+
- Distributed Lock Security
|
|
15
|
+
- GraphQL Security
|
|
16
|
+
- Authentication Patterns
|
|
17
|
+
- Best Practices
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
Security decorators are backend-only decorators that define who can access your data and resources. They:
|
|
22
|
+
- Return a boolean (or `Promise<boolean>`) indicating whether the operation is allowed
|
|
23
|
+
- Give developers full control to implement any business logic needed for authorization
|
|
24
|
+
- Are required when clients don't use an API key
|
|
25
|
+
|
|
26
|
+
**Important:** Clients initialized with an API key **bypass all security rules**. Security rules only apply to clients using authentication providers.
|
|
27
|
+
|
|
28
|
+
## Auth Methods
|
|
29
|
+
|
|
30
|
+
These methods are available in all security rule functions via `this`:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// Get user authentication (JWT token)
|
|
34
|
+
this.getUserAuth() // AuthWithBearer | undefined
|
|
35
|
+
// Returns: { type: 'Bearer', userId: string, expiration: number, attributes: Record<string, any> }
|
|
36
|
+
|
|
37
|
+
// Get API key authentication
|
|
38
|
+
this.getApiKeyAuth() // AuthWithApiKey | undefined
|
|
39
|
+
// Returns: { type: 'ApiKey', apiKey: string }
|
|
40
|
+
|
|
41
|
+
this.isAuthenticated() // boolean - true if user token OR API key
|
|
42
|
+
this.assertIsAuthenticated() // throws if not authenticated
|
|
43
|
+
this.assertApiKeyCall() // throws if not API key auth
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Database Security
|
|
47
|
+
|
|
48
|
+
### @secureDatabase
|
|
49
|
+
|
|
50
|
+
Secures operations across an entire database integration.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { SquidService, secureDatabase, QueryContext, MutationContext } from '@squidcloud/backend';
|
|
54
|
+
|
|
55
|
+
export class MyService extends SquidService {
|
|
56
|
+
@secureDatabase('read')
|
|
57
|
+
allowRead(context: QueryContext<User>): boolean {
|
|
58
|
+
const userId = this.getUserAuth()?.userId;
|
|
59
|
+
if (!userId) return false;
|
|
60
|
+
return context.isSubqueryOf('userId', '==', userId);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@secureDatabase('write')
|
|
64
|
+
allowWrite(context: MutationContext<User>): boolean {
|
|
65
|
+
return this.isAuthenticated();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@secureDatabase('all')
|
|
69
|
+
allowAll(): boolean {
|
|
70
|
+
return this.isAuthenticated();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Secure specific integration
|
|
74
|
+
@secureDatabase('insert', 'my-integration-id')
|
|
75
|
+
allowInsertInIntegration(context: MutationContext): boolean {
|
|
76
|
+
return context.after?.createdBy === 'admin';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Operation Types:** `'read'`, `'write'`, `'update'`, `'insert'`, `'delete'`, `'all'`
|
|
82
|
+
|
|
83
|
+
### @secureCollection
|
|
84
|
+
|
|
85
|
+
Secures operations on a specific collection.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// QueryContext for 'read' operations
|
|
89
|
+
@secureCollection('users', 'read')
|
|
90
|
+
allowUserRead(context: QueryContext): boolean {
|
|
91
|
+
const userId = this.getUserAuth()?.userId;
|
|
92
|
+
if (!userId) return false;
|
|
93
|
+
// Check if query filters on a specific field
|
|
94
|
+
return context.isSubqueryOf('id', '==', userId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// MutationContext for 'insert', 'update', 'delete' operations
|
|
98
|
+
@secureCollection('users', 'update')
|
|
99
|
+
allowUserUpdate(context: MutationContext<User>): boolean {
|
|
100
|
+
const userId = this.getUserAuth()?.userId;
|
|
101
|
+
if (!userId) return false;
|
|
102
|
+
|
|
103
|
+
// context.before: document state before mutation
|
|
104
|
+
// context.after: document state after mutation
|
|
105
|
+
|
|
106
|
+
// Check if specific paths were affected
|
|
107
|
+
if (context.affectsPath('email')) {
|
|
108
|
+
return false; // Don't allow email changes
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check ownership
|
|
112
|
+
return context.after?.id === userId;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Context Types
|
|
117
|
+
|
|
118
|
+
**QueryContext<T>** - Used for 'read' and 'all' operations:
|
|
119
|
+
```typescript
|
|
120
|
+
context.collectionName // Name of the collection being queried
|
|
121
|
+
context.integrationId // Integration ID
|
|
122
|
+
context.limit // Query limit
|
|
123
|
+
context.isSubqueryOf(field, operator, value) // Check if query filters on field
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**MutationContext<T>** - Used for 'insert', 'update', 'delete' operations:
|
|
127
|
+
```typescript
|
|
128
|
+
context.before // Document before mutation (undefined for insert)
|
|
129
|
+
context.after // Document after mutation (undefined for delete)
|
|
130
|
+
context.affectsPath(path) // Check if specific field path was modified
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Both types are exported from `@squidcloud/backend`.
|
|
134
|
+
|
|
135
|
+
## Queue Security
|
|
136
|
+
|
|
137
|
+
### @secureTopic
|
|
138
|
+
|
|
139
|
+
Secures queue topics.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { SquidService, secureTopic, TopicReadContext, TopicWriteContext } from '@squidcloud/backend';
|
|
143
|
+
|
|
144
|
+
// Read security
|
|
145
|
+
@secureTopic('notifications', 'read')
|
|
146
|
+
allowRead(context: TopicReadContext): boolean {
|
|
147
|
+
return this.isAuthenticated();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Write security with message validation
|
|
151
|
+
@secureTopic('notifications', 'write')
|
|
152
|
+
allowWrite(context: TopicWriteContext<Message>): boolean {
|
|
153
|
+
return context.messages.length <= 100;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// For Kafka/Confluent integrations, specify integrationId as third parameter
|
|
157
|
+
@secureTopic('events', 'read', 'kafka-integration')
|
|
158
|
+
allowKafkaRead(context: TopicReadContext): boolean {
|
|
159
|
+
return this.isAuthenticated();
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Types:** `'read'`, `'write'`, `'all'`
|
|
164
|
+
|
|
165
|
+
**Context Types:**
|
|
166
|
+
- `TopicReadContext` - contains `integrationId`, `topicName`
|
|
167
|
+
- `TopicWriteContext<T>` - contains `integrationId`, `topicName`, `messages` array
|
|
168
|
+
|
|
169
|
+
## Storage Security
|
|
170
|
+
|
|
171
|
+
### @secureStorage
|
|
172
|
+
|
|
173
|
+
Secures storage operations.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { SquidService, secureStorage, StorageContext } from '@squidcloud/backend';
|
|
177
|
+
|
|
178
|
+
@secureStorage('write')
|
|
179
|
+
allowWrite(context: StorageContext): boolean {
|
|
180
|
+
return !context.pathsInBucket.some(p => p.startsWith('/admin'));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@secureStorage('read', 'my-s3-integration')
|
|
184
|
+
allowRead(context: StorageContext): boolean {
|
|
185
|
+
return this.isAuthenticated();
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Types:** `'read'`, `'write'`, `'update'`, `'insert'`, `'delete'`, `'all'`
|
|
190
|
+
|
|
191
|
+
## API Security
|
|
192
|
+
|
|
193
|
+
### @secureApi
|
|
194
|
+
|
|
195
|
+
Secures API integration calls.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { SquidService, secureApi, ApiCallContext } from '@squidcloud/backend';
|
|
199
|
+
|
|
200
|
+
// Secure specific endpoint
|
|
201
|
+
@secureApi('stripe-api', 'create-charge')
|
|
202
|
+
allowCharge(context: ApiCallContext): boolean {
|
|
203
|
+
const amount = (context.body as any)?.amount;
|
|
204
|
+
return amount < 10000;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Secure entire API integration
|
|
208
|
+
@secureApi('external-api')
|
|
209
|
+
allowApi(): boolean {
|
|
210
|
+
return this.isAuthenticated();
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Native Query Security
|
|
215
|
+
|
|
216
|
+
### @secureNativeQuery
|
|
217
|
+
|
|
218
|
+
Secures native database queries (SQL, MongoDB aggregation, etc.).
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { SquidService, secureNativeQuery, NativeQueryContext } from '@squidcloud/backend';
|
|
222
|
+
|
|
223
|
+
@secureNativeQuery('postgres-db')
|
|
224
|
+
allowQuery(context: NativeQueryContext): boolean {
|
|
225
|
+
if (context.type === 'relational') {
|
|
226
|
+
// Prevent destructive queries
|
|
227
|
+
return !context.query.toUpperCase().includes('DROP');
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## AI Security
|
|
234
|
+
|
|
235
|
+
### @secureAiQuery
|
|
236
|
+
|
|
237
|
+
Secures AI query execution.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { SquidService, secureAiQuery, AiQueryContext } from '@squidcloud/backend';
|
|
241
|
+
|
|
242
|
+
@secureAiQuery()
|
|
243
|
+
allowAiQuery(context: AiQueryContext): boolean {
|
|
244
|
+
return this.isAuthenticated() && context.prompt.length < 1000;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@secureAiQuery('specific-integration')
|
|
248
|
+
allowSpecificAiQuery(context: AiQueryContext): boolean {
|
|
249
|
+
return this.getUserAuth()?.attributes?.role === 'admin';
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### @secureAiAgent
|
|
254
|
+
|
|
255
|
+
Secures AI agent access.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { SquidService, secureAiAgent, SecureAiAgentContext } from '@squidcloud/backend';
|
|
259
|
+
|
|
260
|
+
// Secure specific agent
|
|
261
|
+
@secureAiAgent('customer-bot')
|
|
262
|
+
allowAgent(context: SecureAiAgentContext): boolean {
|
|
263
|
+
return !context.prompt?.includes('admin');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Secure all agents
|
|
267
|
+
@secureAiAgent()
|
|
268
|
+
allowAllAgents(): boolean {
|
|
269
|
+
return this.isAuthenticated();
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Distributed Lock Security
|
|
274
|
+
|
|
275
|
+
### @secureDistributedLock
|
|
276
|
+
|
|
277
|
+
Secures distributed lock access.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { SquidService, secureDistributedLock, DistributedLockContext } from '@squidcloud/backend';
|
|
281
|
+
|
|
282
|
+
// Secure specific mutex
|
|
283
|
+
@secureDistributedLock('payment-processing')
|
|
284
|
+
allowPaymentLock(context: DistributedLockContext): boolean {
|
|
285
|
+
return this.isAuthenticated() && context.mutex === 'payment-processing';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Secure all mutexes
|
|
289
|
+
@secureDistributedLock()
|
|
290
|
+
allowAllLocks(context: DistributedLockContext): boolean {
|
|
291
|
+
return this.isAuthenticated();
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Important:** Without API key, you must define `@secureDistributedLock` decorators to allow lock acquisition.
|
|
296
|
+
|
|
297
|
+
## GraphQL Security
|
|
298
|
+
|
|
299
|
+
### @secureGraphQL
|
|
300
|
+
|
|
301
|
+
Secures GraphQL operations.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { SquidService, secureGraphQL, GraphqlContext } from '@squidcloud/backend';
|
|
305
|
+
|
|
306
|
+
@secureGraphQL('my-graphql-api')
|
|
307
|
+
allowGraphQL(context: GraphqlContext): boolean {
|
|
308
|
+
return this.isAuthenticated();
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Context:** `GraphqlContext` provides the full context including `query`, `variables`, `operationName`, and `isGraphiQL` (boolean indicating if request is from GraphiQL IDE).
|
|
313
|
+
|
|
314
|
+
## Authentication Patterns
|
|
315
|
+
|
|
316
|
+
### Row-Level Security Example
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
@secureCollection('documents', 'read')
|
|
320
|
+
allowDocumentRead(context: QueryContext<Document>): boolean {
|
|
321
|
+
const userId = this.getUserAuth()?.userId;
|
|
322
|
+
if (!userId) return false;
|
|
323
|
+
|
|
324
|
+
// Users can only read documents they own or that are public
|
|
325
|
+
return context.isSubqueryOf('ownerId', '==', userId) ||
|
|
326
|
+
context.isSubqueryOf('isPublic', '==', true);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@secureCollection('documents', 'update')
|
|
330
|
+
allowDocumentUpdate(context: MutationContext<Document>): boolean {
|
|
331
|
+
const userId = this.getUserAuth()?.userId;
|
|
332
|
+
if (!userId) return false;
|
|
333
|
+
|
|
334
|
+
// Only owner can update
|
|
335
|
+
return context.before?.ownerId === userId;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Role-Based Access Example
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
@secureCollection('admin-data', 'read')
|
|
343
|
+
allowAdminRead(): boolean {
|
|
344
|
+
const role = this.getUserAuth()?.attributes?.role;
|
|
345
|
+
return role === 'admin' || role === 'superadmin';
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Best Practices
|
|
350
|
+
|
|
351
|
+
1. **Always secure collections/topics/storage** - Default to deny
|
|
352
|
+
2. **Validate input in executables** - Check auth and params
|
|
353
|
+
3. **Use `isSubqueryOf()` for read rules** - Ensures queries are properly filtered
|
|
354
|
+
4. **Use `affectsPath()` for write rules** - Control which fields can be modified
|
|
355
|
+
5. **Never trust client data** - Always validate in security rules
|
|
356
|
+
6. **Test security rules thoroughly** - Use different user contexts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squidcloud/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.447",
|
|
4
4
|
"description": "The Squid CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=18.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@squidcloud/local-backend": "^1.0.
|
|
31
|
+
"@squidcloud/local-backend": "^1.0.447",
|
|
32
32
|
"adm-zip": "^0.5.16",
|
|
33
33
|
"copy-webpack-plugin": "^12.0.2",
|
|
34
34
|
"decompress": "^4.2.1",
|