@contentrain/rules 0.3.2 → 0.3.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.
- package/package.json +3 -1
- package/shared/accessibility-rules.md +155 -0
- package/shared/content-conventions.md +314 -0
- package/shared/content-quality.md +294 -0
- package/shared/i18n-quality.md +293 -0
- package/shared/mcp-usage.md +408 -0
- package/shared/media-rules.md +165 -0
- package/shared/normalize-rules.md +335 -0
- package/shared/schema-rules.md +380 -0
- package/shared/security-rules.md +141 -0
- package/shared/seo-rules.md +224 -0
- package/shared/workflow-rules.md +317 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Contentrain Schema Rules
|
|
2
|
+
|
|
3
|
+
> These rules define the Contentrain type system, field definitions, model definitions, and relation system. Follow them exactly when creating or modifying models and content schemas.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Type System Overview
|
|
8
|
+
|
|
9
|
+
Contentrain uses a **flat type system** with 27 types. Each type is a single keyword -- there is no `format` sub-layer. `type: "email"` is the complete specification.
|
|
10
|
+
|
|
11
|
+
This design is optimized for AI agents: easy to produce, cheap to read, fast to validate.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Complete Type Reference (27 Types)
|
|
16
|
+
|
|
17
|
+
### 2.1 String Family (11 types)
|
|
18
|
+
|
|
19
|
+
| Type | Description | Validation | JSON Schema Export |
|
|
20
|
+
|------|-------------|------------|-------------------|
|
|
21
|
+
| `string` | Single-line text | -- | `{ "type": "string" }` |
|
|
22
|
+
| `text` | Multi-line text | -- | `{ "type": "string" }` |
|
|
23
|
+
| `email` | Email address | RFC 5321 | `{ "type": "string", "format": "email" }` |
|
|
24
|
+
| `url` | URL | RFC 3986 | `{ "type": "string", "format": "uri" }` |
|
|
25
|
+
| `slug` | URL-safe identifier | `/^[a-z0-9]+(?:-[a-z0-9]+)*$/` | `{ "type": "string", "pattern": "..." }` |
|
|
26
|
+
| `color` | Hex color code | `/^#[0-9a-fA-F]{6}$/` | `{ "type": "string" }` |
|
|
27
|
+
| `phone` | Phone number | E.164 or freeform | `{ "type": "string" }` |
|
|
28
|
+
| `code` | Code snippet | -- | `{ "type": "string" }` |
|
|
29
|
+
| `icon` | Icon identifier | -- | `{ "type": "string" }` |
|
|
30
|
+
| `markdown` | Markdown content | -- | `{ "type": "string" }` |
|
|
31
|
+
| `richtext` | HTML rich text | -- | `{ "type": "string" }` |
|
|
32
|
+
|
|
33
|
+
### 2.2 Number Family (5 types)
|
|
34
|
+
|
|
35
|
+
| Type | Description | Constraints | JSON Schema Export |
|
|
36
|
+
|------|-------------|-------------|-------------------|
|
|
37
|
+
| `number` | General number | -- | `{ "type": "number" }` |
|
|
38
|
+
| `integer` | Whole number | -- | `{ "type": "integer" }` |
|
|
39
|
+
| `decimal` | Decimal number | -- | `{ "type": "number" }` |
|
|
40
|
+
| `percent` | Percentage | 0-100 | `{ "type": "number", "minimum": 0, "maximum": 100 }` |
|
|
41
|
+
| `rating` | Rating score | 1-5 | `{ "type": "integer", "minimum": 1, "maximum": 5 }` |
|
|
42
|
+
|
|
43
|
+
### 2.3 Primitives (3 types)
|
|
44
|
+
|
|
45
|
+
| Type | Storage Format | JSON Schema Export |
|
|
46
|
+
|------|---------------|-------------------|
|
|
47
|
+
| `boolean` | `true` / `false` | `{ "type": "boolean" }` |
|
|
48
|
+
| `date` | `"YYYY-MM-DD"` string | `{ "type": "string", "format": "date" }` |
|
|
49
|
+
| `datetime` | ISO 8601 string | `{ "type": "string", "format": "date-time" }` |
|
|
50
|
+
|
|
51
|
+
### 2.4 Media (3 types)
|
|
52
|
+
|
|
53
|
+
| Type | Storage | Description |
|
|
54
|
+
|------|---------|-------------|
|
|
55
|
+
| `image` | Relative path (string) | Image file reference |
|
|
56
|
+
| `video` | Relative path (string) | Video file reference |
|
|
57
|
+
| `file` | Relative path (string) | Generic file reference |
|
|
58
|
+
|
|
59
|
+
In v1, media fields store URL/path strings only. Upload and processing are out of scope.
|
|
60
|
+
|
|
61
|
+
### 2.5 Relations (2 types)
|
|
62
|
+
|
|
63
|
+
| Type | Cardinality | Storage |
|
|
64
|
+
|------|-------------|---------|
|
|
65
|
+
| `relation` | One-to-one | `"entry-id"` (string) |
|
|
66
|
+
| `relations` | One-to-many | `["id-1", "id-2"]` (string array) |
|
|
67
|
+
|
|
68
|
+
### 2.6 Structural (3 types)
|
|
69
|
+
|
|
70
|
+
| Type | Description | Requires |
|
|
71
|
+
|------|-------------|----------|
|
|
72
|
+
| `select` | Fixed options, pick one | `options` property |
|
|
73
|
+
| `array` | Ordered list of items | `items` property |
|
|
74
|
+
| `object` | Nested key-value structure | `fields` property |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. Field Definition
|
|
79
|
+
|
|
80
|
+
A field definition describes one field in a model. Include only the properties that apply -- omit all defaults.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"type": "string",
|
|
85
|
+
"required": true,
|
|
86
|
+
"min": 3,
|
|
87
|
+
"max": 100,
|
|
88
|
+
"unique": true,
|
|
89
|
+
"description": "Page title"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3.1 Field Properties
|
|
94
|
+
|
|
95
|
+
| Property | Applicable Types | Description |
|
|
96
|
+
|----------|-----------------|-------------|
|
|
97
|
+
| `type` | ALL | **Required.** One of the 27 types. |
|
|
98
|
+
| `required` | ALL | Mark field as mandatory. Default: `false`. Omit if `false`. |
|
|
99
|
+
| `unique` | `string`, `email`, `slug`, `integer` | Enforce uniqueness within model. Default: `false`. Omit if `false`. |
|
|
100
|
+
| `default` | ALL | Default value. Omit if `null`. |
|
|
101
|
+
| `min` | string/text: char count; numbers: value; array: element count | Minimum constraint. |
|
|
102
|
+
| `max` | Same as `min` | Maximum constraint. |
|
|
103
|
+
| `pattern` | `string`, `text`, `code` | Regex validation pattern. |
|
|
104
|
+
| `options` | `select` ONLY | Fixed choices: `["draft", "published", "archived"]` |
|
|
105
|
+
| `model` | `relation`, `relations` ONLY | Target model ID. String or string array for polymorphic. |
|
|
106
|
+
| `items` | `array` ONLY | Element type: `"string"` or `{ "type": "object", "fields": {...} }` |
|
|
107
|
+
| `fields` | `object` ONLY | Nested field definitions. |
|
|
108
|
+
| `accept` | `image`, `video`, `file` | Allowed MIME types: `"image/png,image/jpeg"` |
|
|
109
|
+
| `maxSize` | `image`, `video`, `file` | Maximum file size in bytes. |
|
|
110
|
+
| `description` | ALL | Human-readable hint (shown in Studio UI tooltip, used as agent context). |
|
|
111
|
+
|
|
112
|
+
### 3.2 Omission Rules
|
|
113
|
+
|
|
114
|
+
These rules produce minimal, clean schema definitions:
|
|
115
|
+
|
|
116
|
+
- If `required` is `false` (the default), do NOT include it.
|
|
117
|
+
- If `unique` is `false` (the default), do NOT include it.
|
|
118
|
+
- If `default` is `null`, do NOT include it.
|
|
119
|
+
- Only include properties that add information. Fewer properties = fewer tokens = faster agent processing.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 4. Model Definition
|
|
124
|
+
|
|
125
|
+
Model definitions live at `.contentrain/models/{model-id}.json`. One file per model.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"id": "blog-post",
|
|
130
|
+
"name": "Blog Post",
|
|
131
|
+
"kind": "document",
|
|
132
|
+
"domain": "blog",
|
|
133
|
+
"i18n": true,
|
|
134
|
+
"description": "Blog articles with markdown body",
|
|
135
|
+
"fields": {
|
|
136
|
+
"title": { "type": "string", "required": true, "max": 120 },
|
|
137
|
+
"slug": { "type": "slug", "required": true, "unique": true },
|
|
138
|
+
"author": { "type": "relation", "model": "team-members", "required": true },
|
|
139
|
+
"tags": { "type": "array", "items": "string" }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 4.1 Model Properties
|
|
145
|
+
|
|
146
|
+
| Property | Type | Required | Description |
|
|
147
|
+
|----------|------|----------|-------------|
|
|
148
|
+
| `id` | `string` | Yes | Unique identifier, **kebab-case**. |
|
|
149
|
+
| `name` | `string` | Yes | Human-readable display name. |
|
|
150
|
+
| `kind` | `string` | Yes | One of: `singleton`, `collection`, `document`, `dictionary`. |
|
|
151
|
+
| `domain` | `string` | Yes | Organizational group (maps to content subdirectory). |
|
|
152
|
+
| `i18n` | `boolean` | Yes | Whether the model supports multiple locales. |
|
|
153
|
+
| `description` | `string` | No | Model description for documentation and agent context. |
|
|
154
|
+
| `fields` | `object` | Yes (except dictionary) | Field definitions. Dictionary kind has NO fields. |
|
|
155
|
+
| `content_path` | `string` | No | Framework-relative path for content files (e.g., `"content/blog"`, `"locales"`). When set, content is written here instead of `.contentrain/content/`. |
|
|
156
|
+
| `locale_strategy` | `string` | No | How locale is encoded in file names: `"file"` (default), `"suffix"`, `"directory"`, `"none"`. |
|
|
157
|
+
|
|
158
|
+
### 4.2 System Fields
|
|
159
|
+
|
|
160
|
+
Do NOT define these in the schema. The platform manages them automatically:
|
|
161
|
+
|
|
162
|
+
| Field | Kind | Source |
|
|
163
|
+
|-------|------|--------|
|
|
164
|
+
| `id` | Collection | 12-char hex, used as object-map key |
|
|
165
|
+
| `slug` | Document | Directory name in content path |
|
|
166
|
+
| `createdAt` / `updatedAt` | All | Derived from Git commit history (not stored) |
|
|
167
|
+
| `status`, `source`, `updated_by`, `approved_by` | All | Stored in `.contentrain/meta/` |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 4.3 Locale Strategy Rules
|
|
172
|
+
|
|
173
|
+
The `locale_strategy` property controls how locale is encoded in file paths:
|
|
174
|
+
|
|
175
|
+
| Strategy | i18n:true JSON path | i18n:true Document path | i18n:false path |
|
|
176
|
+
|----------|---------------------|------------------------|-----------------|
|
|
177
|
+
| `file` (default) | `{dir}/{locale}.json` | `{dir}/{slug}/{locale}.md` | `{dir}/data.json` |
|
|
178
|
+
| `suffix` | `{dir}/{model}.{locale}.json` | `{dir}/{slug}.{locale}.md` | `{dir}/data.json` |
|
|
179
|
+
| `directory` | `{dir}/{locale}/{model}.json` | `{dir}/{locale}/{slug}.md` | `{dir}/data.json` |
|
|
180
|
+
| `none` | **INVALID** (requires i18n:false) | **INVALID** | `{dir}/{model}.json` or `{dir}/{slug}.md` |
|
|
181
|
+
|
|
182
|
+
- `locale_strategy: "none"` requires `i18n: false`. The "none" strategy stores a single file without locale encoding.
|
|
183
|
+
- When `content_path` is set, `{dir}` is the content_path. Otherwise `{dir}` is `.contentrain/content/{domain}/{model-id}`.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 5. The Four Model Kinds
|
|
188
|
+
|
|
189
|
+
### 5.1 Singleton
|
|
190
|
+
|
|
191
|
+
**One instance per locale.** Use for page sections, site config, navigation.
|
|
192
|
+
|
|
193
|
+
| Aspect | Detail |
|
|
194
|
+
|--------|--------|
|
|
195
|
+
| Storage | JSON object (one file per locale) |
|
|
196
|
+
| File path | `content/{domain}/{model-id}/{locale}.json` |
|
|
197
|
+
| `i18n: false` path | `content/{domain}/{model-id}/data.json` |
|
|
198
|
+
| ID management | None (single instance) |
|
|
199
|
+
| Relation target | Cannot be referenced by relations |
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"cta": "Get Started",
|
|
204
|
+
"title": "Build faster"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 5.2 Collection
|
|
209
|
+
|
|
210
|
+
**Multiple entries.** Use for team members, products, FAQs, categories.
|
|
211
|
+
|
|
212
|
+
| Aspect | Detail |
|
|
213
|
+
|--------|--------|
|
|
214
|
+
| Storage | JSON object-map (entry ID as key, sorted lexicographically) |
|
|
215
|
+
| File path | `content/{domain}/{model-id}/{locale}.json` |
|
|
216
|
+
| Tool output | Array with `id` injected |
|
|
217
|
+
| ID management | Auto-generated 12-char hex |
|
|
218
|
+
| Relation target | Referenced by entry `id` |
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"a1b2c3d4e5f6": { "name": "Ahmet", "role": "CEO" },
|
|
223
|
+
"f6e5d4c3b2a1": { "name": "Jane", "role": "CTO" }
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 5.3 Document
|
|
228
|
+
|
|
229
|
+
**Markdown with frontmatter.** Use for blog posts, documentation, changelogs.
|
|
230
|
+
|
|
231
|
+
| Aspect | Detail |
|
|
232
|
+
|--------|--------|
|
|
233
|
+
| Storage | `.md` file with YAML frontmatter |
|
|
234
|
+
| File path | `content/{domain}/{model-id}/{slug}/{locale}.md` |
|
|
235
|
+
| `i18n: false` path | `content/{domain}/{model-id}/{slug}.md` |
|
|
236
|
+
| ID management | `slug` field (URL-safe, unique) |
|
|
237
|
+
| Relation target | Referenced by `slug` |
|
|
238
|
+
|
|
239
|
+
```markdown
|
|
240
|
+
---
|
|
241
|
+
title: Getting Started
|
|
242
|
+
slug: getting-started
|
|
243
|
+
author: a1b2c3d4e5f6
|
|
244
|
+
---
|
|
245
|
+
# Getting Started with Contentrain
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 5.4 Dictionary
|
|
249
|
+
|
|
250
|
+
**Flat key-value pairs.** Use for error messages, UI strings, translations.
|
|
251
|
+
|
|
252
|
+
| Aspect | Detail |
|
|
253
|
+
|--------|--------|
|
|
254
|
+
| Storage | Flat JSON object (key-value, all values are strings) |
|
|
255
|
+
| File path | `content/{domain}/{model-id}/{locale}.json` |
|
|
256
|
+
| Fields definition | None -- dictionary models have NO `fields` property |
|
|
257
|
+
| ID management | Key is identity |
|
|
258
|
+
| Relation target | Cannot be referenced by relations |
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"auth.expired": "Session expired",
|
|
263
|
+
"auth.failed": "Authentication failed"
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 6. Relation Rules
|
|
270
|
+
|
|
271
|
+
### 6.1 Basic Relations
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
"author": { "type": "relation", "model": "team-members", "required": true }
|
|
275
|
+
"categories": { "type": "relations", "model": "categories" }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
- `relation` (1:1): Value is a single string -- an entry ID or slug.
|
|
279
|
+
- `relations` (1:many): Value is a string array of entry IDs or slugs.
|
|
280
|
+
|
|
281
|
+
### 6.2 Target Model Restrictions
|
|
282
|
+
|
|
283
|
+
| Target Kind | Reference Key | Can Be Target? |
|
|
284
|
+
|-------------|--------------|----------------|
|
|
285
|
+
| Collection | Entry `id` | Yes |
|
|
286
|
+
| Document | Document `slug` | Yes |
|
|
287
|
+
| Singleton | -- | No |
|
|
288
|
+
| Dictionary | -- | No |
|
|
289
|
+
|
|
290
|
+
### 6.3 Polymorphic Relations
|
|
291
|
+
|
|
292
|
+
When a field can reference multiple model types:
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
"target": { "type": "relation", "model": ["blog-post", "page"] }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Storage for polymorphic references uses a compound value:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{ "model": "blog-post", "ref": "getting-started" }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 6.4 Self-Referencing
|
|
305
|
+
|
|
306
|
+
Models can reference themselves (e.g., hierarchical categories):
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
"parent": { "type": "relation", "model": "categories" }
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 6.5 Resolution Rules
|
|
313
|
+
|
|
314
|
+
- Relations are resolved **1 level deep** -- no recursive resolution.
|
|
315
|
+
- **Unresolved IDs** (target entry does not exist) are kept as raw strings (graceful degradation).
|
|
316
|
+
- **Cascade deletion does not exist.** Deleting a referenced entry produces a broken relation warning.
|
|
317
|
+
- **Array order is preserved** for `relations` type.
|
|
318
|
+
- **IDs/slugs are locale-agnostic.** The same reference works across all locales.
|
|
319
|
+
|
|
320
|
+
### 6.6 Validation
|
|
321
|
+
|
|
322
|
+
- Referenced ID/slug MUST exist in the target model.
|
|
323
|
+
- `model` property MUST reference an existing model ID.
|
|
324
|
+
- Referential integrity is checked by `contentrain_validate`.
|
|
325
|
+
- Deleting a model that is referenced by other models is BLOCKED.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 7. Nesting Limits
|
|
330
|
+
|
|
331
|
+
### 7.1 Object Type
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
"address": {
|
|
335
|
+
"type": "object",
|
|
336
|
+
"fields": {
|
|
337
|
+
"city": { "type": "string", "required": true },
|
|
338
|
+
"street": { "type": "string", "required": true },
|
|
339
|
+
"zip": { "type": "string" }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### 7.2 Array of Objects
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
"variants": {
|
|
348
|
+
"type": "array",
|
|
349
|
+
"items": {
|
|
350
|
+
"type": "object",
|
|
351
|
+
"fields": {
|
|
352
|
+
"color": { "type": "color", "required": true },
|
|
353
|
+
"price": { "type": "decimal", "required": true },
|
|
354
|
+
"size": { "type": "select", "options": ["S", "M", "L"] }
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
"max": 50
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 7.3 Depth Limit
|
|
362
|
+
|
|
363
|
+
**Maximum nesting depth: 2 levels.** An object inside an object is allowed. An object inside an object inside an object is NOT.
|
|
364
|
+
|
|
365
|
+
- Prefer flat types over deeply nested structures.
|
|
366
|
+
- Use relations to model complex data relationships instead of nesting.
|
|
367
|
+
- If you need deeper nesting, create a separate model and use a `relation` field.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 8. Naming Conventions
|
|
372
|
+
|
|
373
|
+
| Element | Convention | Example |
|
|
374
|
+
|---------|-----------|---------|
|
|
375
|
+
| Model ID | kebab-case | `blog-post`, `team-members` |
|
|
376
|
+
| Field key | snake_case | `hero_image`, `cta_url` |
|
|
377
|
+
| Domain | lowercase, single word or kebab-case | `blog`, `marketing`, `system` |
|
|
378
|
+
| Dictionary key | dot-notation | `auth.failed`, `validation.required` |
|
|
379
|
+
| Slug | lowercase, kebab-case | `getting-started` |
|
|
380
|
+
| Locale | ISO 639-1 | `en`, `tr`, `de` |
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Security Rules
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY for all AI agents creating or editing content in Contentrain projects.
|
|
4
|
+
Content that violates these rules MUST be rejected. These rules protect against XSS, data leaks, and injection attacks.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## XSS Prevention in Content
|
|
9
|
+
|
|
10
|
+
1. `richtext` and `markdown` fields MUST NOT contain any of the following:
|
|
11
|
+
- `<script>` or `</script>` tags
|
|
12
|
+
- `javascript:` protocol in any attribute value
|
|
13
|
+
- Event handler attributes: `onerror`, `onload`, `onclick`, `onmouseover`, `onfocus`, `onblur`, `onsubmit`, `onchange`, `onkeydown`, `onkeyup`, `onkeypress`, `ondblclick`, `onmousedown`, `onmouseup`, `onmousemove`, `onmouseout`, `oncontextmenu`, `ondrag`, `ondrop`, `onpaste`, `oninput`
|
|
14
|
+
- `<iframe>`, `<embed>`, `<object>`, `<applet>`, `<form>`, `<input>`, `<textarea>`, `<select>`, `<button>` tags
|
|
15
|
+
- `data:` URIs containing executable content (e.g., `data:text/html`, `data:application/javascript`)
|
|
16
|
+
- CSS expressions: `expression()`, `url(javascript:)`, `-moz-binding`
|
|
17
|
+
- `<base>` tag (can redirect all relative URLs)
|
|
18
|
+
- `<meta>` tag with `http-equiv="refresh"`
|
|
19
|
+
- `<svg>` with embedded scripts or event handlers
|
|
20
|
+
|
|
21
|
+
2. **Allowed HTML tags** in richtext fields:
|
|
22
|
+
```
|
|
23
|
+
p, strong, em, a, ul, ol, li, h2, h3, h4, h5, h6,
|
|
24
|
+
blockquote, code, pre, img, br, hr,
|
|
25
|
+
table, thead, tbody, tr, th, td,
|
|
26
|
+
del, ins, sup, sub, abbr, mark, details, summary
|
|
27
|
+
```
|
|
28
|
+
Any tag not on this list MUST be stripped or rejected.
|
|
29
|
+
|
|
30
|
+
3. **Allowed attributes on `<a>` tags**: `href`, `title`, `target`.
|
|
31
|
+
- `href` MUST use `http://`, `https://`, or `mailto:` protocol only.
|
|
32
|
+
- `target` MUST be `_blank` or `_self` only.
|
|
33
|
+
- When `target="_blank"`, the frontend should add `rel="noopener noreferrer"` — but the content itself must not rely on this.
|
|
34
|
+
|
|
35
|
+
4. **Allowed attributes on `<img>` tags**: `src`, `alt`, `width`, `height`.
|
|
36
|
+
- `src` MUST use `https://`, `http://`, or a relative path starting with `/` or `./`.
|
|
37
|
+
- No `data:` URIs for `src` in production content.
|
|
38
|
+
|
|
39
|
+
5. Strip or reject all other HTML attributes not explicitly allowed above.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Secret Detection
|
|
44
|
+
|
|
45
|
+
1. NEVER include any of the following in ANY content field:
|
|
46
|
+
- **API keys**: patterns matching `sk-`, `pk_`, `sk_live_`, `sk_test_`, `api_key=`, `apiKey:`, `x-api-key`
|
|
47
|
+
- **Access tokens**: JWT tokens (`eyJ...`), Bearer tokens, OAuth tokens, session tokens
|
|
48
|
+
- **Passwords or credentials**: any field value resembling `password=`, `passwd:`, `secret=`
|
|
49
|
+
- **Private keys**: `-----BEGIN RSA PRIVATE KEY-----`, `-----BEGIN OPENSSH PRIVATE KEY-----`, `-----BEGIN PGP PRIVATE KEY BLOCK-----`, or similar PEM blocks
|
|
50
|
+
- **Database connection strings**: `mongodb://`, `postgres://`, `mysql://`, `redis://` with credentials
|
|
51
|
+
- **Cloud credentials**: AWS (`AKIA...`), GCP (`AIza...`), Azure storage keys
|
|
52
|
+
- **Webhook URLs with tokens**: URLs containing `token=`, `secret=`, or `/hooks/` with embedded credentials
|
|
53
|
+
|
|
54
|
+
2. If a field value matches any secret pattern, **reject the value** and warn the user: `"This value appears to contain a secret or credential. Secrets must not be stored in content fields."`
|
|
55
|
+
|
|
56
|
+
3. Environment-specific values (API endpoints, service IDs) SHOULD use placeholder references (e.g., `{{API_URL}}`) or be stored in environment configuration, not hardcoded in content.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## PII Handling
|
|
61
|
+
|
|
62
|
+
1. **Flag** when email addresses appear in fields that are not typed as `email`. The agent should warn: `"Email address detected in a non-email field. Confirm this is intentional."`
|
|
63
|
+
|
|
64
|
+
2. **Flag** when phone numbers appear in fields that are not typed as `phone`.
|
|
65
|
+
|
|
66
|
+
3. **Flag** physical addresses, government ID numbers (SSN, national ID), and dates of birth when they appear in generic `string` or `richtext` fields.
|
|
67
|
+
|
|
68
|
+
4. User-generated content fields SHOULD be marked for frontend sanitization in the model metadata.
|
|
69
|
+
|
|
70
|
+
5. NEVER store authentication credentials (usernames, passwords, tokens) in content fields.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## URL Validation
|
|
75
|
+
|
|
76
|
+
1. All URLs in `url` type fields MUST use `https://` protocol.
|
|
77
|
+
- **Exception**: `http://localhost` and `http://127.0.0.1` URLs are allowed for development references.
|
|
78
|
+
|
|
79
|
+
2. Reject URLs containing path traversal sequences: `../`, `..\\`, `%2e%2e/`, `%2e%2e%5c`.
|
|
80
|
+
|
|
81
|
+
3. URLs MUST have a valid format: protocol + domain + valid TLD. Reject malformed URLs.
|
|
82
|
+
|
|
83
|
+
4. Do not use IP-based URLs in production content. Use domain names.
|
|
84
|
+
- **Exception**: private/internal documentation that explicitly references infrastructure IPs.
|
|
85
|
+
|
|
86
|
+
5. Reject `file://` protocol URLs. Content must never reference local filesystem paths as URLs.
|
|
87
|
+
|
|
88
|
+
6. Reject `ftp://` protocol URLs unless the content model explicitly documents FTP support.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## File & Media Security
|
|
93
|
+
|
|
94
|
+
1. Media paths MUST be relative to the `assets_path` defined in `config.json`. Never use absolute filesystem paths.
|
|
95
|
+
|
|
96
|
+
2. Reject any file path containing:
|
|
97
|
+
- Absolute system paths: `/etc/`, `/usr/`, `/var/`, `C:\\`, `C:/`, `/Users/`, `/home/`
|
|
98
|
+
- Path traversal: `../`, `..\\`, `%2e%2e`
|
|
99
|
+
|
|
100
|
+
3. Validate file extensions match the field type:
|
|
101
|
+
- `image` fields: `.webp`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.avif`
|
|
102
|
+
- `video` fields: `.mp4`, `.webm`, `.ogg`
|
|
103
|
+
- `file` fields: any extension, but **flag executables** and warn the user: `.exe`, `.sh`, `.bat`, `.cmd`, `.ps1`, `.msi`, `.dmg`, `.app`, `.jar`, `.dll`, `.so`
|
|
104
|
+
|
|
105
|
+
4. File names MUST NOT contain spaces or special characters. Only alphanumeric characters, hyphens (`-`), underscores (`_`), and dots (`.`) are allowed.
|
|
106
|
+
|
|
107
|
+
5. Reject files with double extensions that disguise executables: `report.pdf.exe`, `image.jpg.sh`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Content Injection
|
|
112
|
+
|
|
113
|
+
1. Values in `object` and `array` type fields MUST be valid JSON. Reject values with:
|
|
114
|
+
- Trailing commas
|
|
115
|
+
- JavaScript comments (`//`, `/* */`)
|
|
116
|
+
- Unquoted keys
|
|
117
|
+
- Single-quoted strings
|
|
118
|
+
- Executable code patterns (function declarations, `eval()`, `require()`, `import()`)
|
|
119
|
+
|
|
120
|
+
2. `code` type fields are displayed as code and never executed, but still apply these rules:
|
|
121
|
+
- No embedded secrets (see Secret Detection above)
|
|
122
|
+
- Content is stored as-is — no sanitization needed beyond secret detection
|
|
123
|
+
|
|
124
|
+
3. Template literals and interpolation patterns (`${...}`, `{{...}}`, `{%...%}`) in content fields MUST be intentional. Flag unexpected template syntax in plain text fields.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Markdown-Specific
|
|
129
|
+
|
|
130
|
+
1. Fenced code blocks (`` ``` ``) are rendered as code, not executed. They are safe from an execution standpoint but still subject to secret detection rules.
|
|
131
|
+
|
|
132
|
+
2. Raw HTML embedded in markdown MUST follow the same XSS rules as richtext fields. Apply the allowed-tags whitelist.
|
|
133
|
+
|
|
134
|
+
3. Link destinations in markdown (`[text](url)`) MUST use `http://`, `https://`, or `mailto:` protocol only. Reject `javascript:`, `data:`, `vbscript:`, and `file:` protocols.
|
|
135
|
+
|
|
136
|
+
4. Image sources in markdown (``) MUST use valid paths:
|
|
137
|
+
- No path traversal (`../`)
|
|
138
|
+
- Valid image extensions (`.webp`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.avif`)
|
|
139
|
+
- `https://` for external images, relative paths for local images
|
|
140
|
+
|
|
141
|
+
5. Do not allow HTML comments (`<!-- -->`) to hide content in markdown — they may be rendered in some parsers and can be used to smuggle payloads.
|