contextos-agents 2.0.0 → 2.1.1

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 (223) hide show
  1. package/.agents/AGENTS.md +53 -33
  2. package/.agents/adapters/aider/export.js +41 -14
  3. package/.agents/adapters/claude/export.js +54 -3
  4. package/.agents/adapters/copilot/export.js +1 -1
  5. package/.agents/adapters/cursor/export.js +1 -1
  6. package/.agents/adapters/drift-detector.js +86 -10
  7. package/.agents/adapters/gemini/export.js +1 -1
  8. package/.agents/adapters/pure-compiler.js +28 -6
  9. package/.agents/adapters/shared.js +13 -4
  10. package/.agents/adapters/zed/export.js +1 -1
  11. package/.agents/compiled/registry.v2.json +29 -25
  12. package/.agents/compiled/registry.v2.sha256 +1 -1
  13. package/.agents/core/skills/context-os/SKILL.md +34 -37
  14. package/.agents/core/skills/engineering-workflow/SKILL.md +24 -24
  15. package/.agents/core/skills/gemini-precision/EXAMPLES.md +72 -0
  16. package/.agents/core/skills/gemini-precision/SKILL.md +2 -1
  17. package/.agents/core/skills/gemini-precision/TROUBLESHOOTING.md +25 -0
  18. package/.agents/core/skills/gemini-precision/skill.yaml +2 -0
  19. package/.agents/core/skills/gstack-roles/SKILL.md +7 -6
  20. package/.agents/core/skills/security/SKILL.md +44 -16
  21. package/.agents/core/skills/security/skill.yaml +0 -1
  22. package/.agents/ctx.js +20 -14
  23. package/.agents/generated/claude/skills/context-os/SKILL.md +34 -37
  24. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +24 -24
  25. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +102 -1
  26. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +7 -6
  27. package/.agents/generated/claude/skills/security/SKILL.md +44 -16
  28. package/.agents/generated/gemini/skills/context-os/SKILL.md +34 -37
  29. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +24 -24
  30. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +105 -1
  31. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +7 -6
  32. package/.agents/generated/gemini/skills/security/SKILL.md +44 -125
  33. package/.agents/plugins.js +105 -8
  34. package/.agents/profiles.js +32 -11
  35. package/.agents/resolver/canonical-resolver.js +7 -7
  36. package/.agents/validate.js +69 -1
  37. package/README.md +81 -24
  38. package/bin/commands/hook.js +167 -0
  39. package/bin/commands/scan.js +77 -0
  40. package/bin/commands.js +39 -1
  41. package/bin/index.js +151 -34
  42. package/bin/lib/gate.js +171 -0
  43. package/bin/lib/git-snapshot.js +214 -0
  44. package/bin/lib/scan.js +461 -0
  45. package/catalog/skills/adapters/EXAMPLES.md +19 -0
  46. package/catalog/skills/adapters/SKILL.md +101 -0
  47. package/catalog/skills/adapters/TROUBLESHOOTING.md +7 -0
  48. package/catalog/skills/adapters/VALIDATION.json +12 -0
  49. package/catalog/skills/adapters/skill.yaml +13 -0
  50. package/catalog/skills/api-design/EXAMPLES.md +91 -0
  51. package/catalog/skills/api-design/SKILL.md +63 -0
  52. package/catalog/skills/api-design/TROUBLESHOOTING.md +54 -0
  53. package/catalog/skills/api-design/VALIDATION.json +11 -0
  54. package/catalog/skills/api-design/skill.yaml +14 -0
  55. package/catalog/skills/architecture-diagrams/SKILL.md +108 -0
  56. package/catalog/skills/architecture-diagrams/VALIDATION.json +12 -0
  57. package/catalog/skills/architecture-diagrams/skill.yaml +9 -0
  58. package/catalog/skills/brutalist-design/EXAMPLES.md +59 -0
  59. package/catalog/skills/brutalist-design/SKILL.md +150 -0
  60. package/catalog/skills/brutalist-design/VALIDATION.json +12 -0
  61. package/catalog/skills/brutalist-design/skill.yaml +10 -0
  62. package/catalog/skills/ci-cd/EXAMPLES.md +79 -0
  63. package/catalog/skills/ci-cd/SKILL.md +69 -0
  64. package/catalog/skills/ci-cd/TROUBLESHOOTING.md +52 -0
  65. package/catalog/skills/ci-cd/VALIDATION.json +11 -0
  66. package/catalog/skills/ci-cd/skill.yaml +13 -0
  67. package/catalog/skills/database/EXAMPLES.md +74 -0
  68. package/catalog/skills/database/SKILL.md +101 -0
  69. package/catalog/skills/database/TROUBLESHOOTING.md +18 -0
  70. package/catalog/skills/database/VALIDATION.json +11 -0
  71. package/catalog/skills/database/skill.yaml +14 -0
  72. package/catalog/skills/ddd/EXAMPLES.md +42 -0
  73. package/catalog/skills/ddd/SKILL.md +247 -0
  74. package/catalog/skills/ddd/TROUBLESHOOTING.md +19 -0
  75. package/catalog/skills/ddd/VALIDATION.json +12 -0
  76. package/catalog/skills/ddd/skill.yaml +14 -0
  77. package/catalog/skills/decisions/EXAMPLES.md +35 -0
  78. package/catalog/skills/decisions/SKILL.md +90 -0
  79. package/catalog/skills/decisions/TROUBLESHOOTING.md +13 -0
  80. package/catalog/skills/decisions/VALIDATION.json +12 -0
  81. package/catalog/skills/decisions/skill.yaml +13 -0
  82. package/catalog/skills/docker/EXAMPLES.md +56 -0
  83. package/catalog/skills/docker/SKILL.md +169 -0
  84. package/catalog/skills/docker/TROUBLESHOOTING.md +18 -0
  85. package/catalog/skills/docker/VALIDATION.json +11 -0
  86. package/catalog/skills/docker/skill.yaml +13 -0
  87. package/catalog/skills/fastapi/EXAMPLES.md +36 -0
  88. package/catalog/skills/fastapi/SKILL.md +171 -0
  89. package/catalog/skills/fastapi/TROUBLESHOOTING.md +19 -0
  90. package/catalog/skills/fastapi/VALIDATION.json +12 -0
  91. package/catalog/skills/fastapi/skill.yaml +14 -0
  92. package/catalog/skills/generators/EXAMPLES.md +19 -0
  93. package/catalog/skills/generators/SKILL.md +110 -0
  94. package/catalog/skills/generators/TROUBLESHOOTING.md +7 -0
  95. package/catalog/skills/generators/VALIDATION.json +12 -0
  96. package/catalog/skills/generators/skill.yaml +22 -0
  97. package/catalog/skills/generators/templates/API.md +77 -0
  98. package/catalog/skills/generators/templates/ARCHITECTURE.md +70 -0
  99. package/catalog/skills/generators/templates/DATABASE.md +42 -0
  100. package/catalog/skills/generators/templates/DECISION.md +46 -0
  101. package/catalog/skills/generators/templates/PRD.md +67 -0
  102. package/catalog/skills/generators/templates/PROJECT_GRAPH.md +56 -0
  103. package/catalog/skills/generators/templates/ROADMAP.md +51 -0
  104. package/catalog/skills/generators/templates/TASKS.md +43 -0
  105. package/catalog/skills/generators/templates/UI.md +73 -0
  106. package/catalog/skills/graphify/EXAMPLES.md +73 -0
  107. package/catalog/skills/graphify/SKILL.md +130 -0
  108. package/catalog/skills/graphify/VALIDATION.json +12 -0
  109. package/catalog/skills/graphify/skill.yaml +13 -0
  110. package/catalog/skills/impeccable-design/EXAMPLES.md +26 -0
  111. package/catalog/skills/impeccable-design/SKILL.md +201 -0
  112. package/catalog/skills/impeccable-design/TROUBLESHOOTING.md +19 -0
  113. package/catalog/skills/impeccable-design/VALIDATION.json +12 -0
  114. package/catalog/skills/impeccable-design/skill.yaml +15 -0
  115. package/catalog/skills/interview-me/SKILL.md +97 -0
  116. package/catalog/skills/interview-me/VALIDATION.json +12 -0
  117. package/catalog/skills/interview-me/skill.yaml +9 -0
  118. package/catalog/skills/microservices/EXAMPLES.md +38 -0
  119. package/catalog/skills/microservices/SKILL.md +164 -0
  120. package/catalog/skills/microservices/TROUBLESHOOTING.md +19 -0
  121. package/catalog/skills/microservices/VALIDATION.json +12 -0
  122. package/catalog/skills/microservices/skill.yaml +14 -0
  123. package/catalog/skills/minimalist-design/EXAMPLES.md +58 -0
  124. package/catalog/skills/minimalist-design/SKILL.md +113 -0
  125. package/catalog/skills/minimalist-design/VALIDATION.json +12 -0
  126. package/catalog/skills/minimalist-design/skill.yaml +10 -0
  127. package/catalog/skills/nestjs/EXAMPLES.md +40 -0
  128. package/catalog/skills/nestjs/SKILL.md +139 -0
  129. package/catalog/skills/nestjs/TROUBLESHOOTING.md +19 -0
  130. package/catalog/skills/nestjs/VALIDATION.json +12 -0
  131. package/catalog/skills/nestjs/skill.yaml +14 -0
  132. package/catalog/skills/nextjs/EXAMPLES.md +40 -0
  133. package/catalog/skills/nextjs/SKILL.md +163 -0
  134. package/catalog/skills/nextjs/TROUBLESHOOTING.md +19 -0
  135. package/catalog/skills/nextjs/VALIDATION.json +12 -0
  136. package/catalog/skills/nextjs/skill.yaml +14 -0
  137. package/catalog/skills/node/EXAMPLES.md +80 -0
  138. package/catalog/skills/node/SKILL.md +128 -0
  139. package/catalog/skills/node/TROUBLESHOOTING.md +19 -0
  140. package/catalog/skills/node/VALIDATION.json +12 -0
  141. package/catalog/skills/node/skill.yaml +14 -0
  142. package/catalog/skills/performance/EXAMPLES.md +30 -0
  143. package/catalog/skills/performance/SKILL.md +75 -0
  144. package/catalog/skills/performance/TROUBLESHOOTING.md +19 -0
  145. package/catalog/skills/performance/VALIDATION.json +12 -0
  146. package/catalog/skills/performance/skill.yaml +14 -0
  147. package/catalog/skills/react/EXAMPLES.md +79 -0
  148. package/catalog/skills/react/SKILL.md +132 -0
  149. package/catalog/skills/react/TROUBLESHOOTING.md +19 -0
  150. package/catalog/skills/react/VALIDATION.json +12 -0
  151. package/catalog/skills/react/skill.yaml +14 -0
  152. package/catalog/skills/react-best-practices/SKILL.md +158 -0
  153. package/catalog/skills/react-best-practices/VALIDATION.json +12 -0
  154. package/catalog/skills/react-best-practices/skill.yaml +13 -0
  155. package/catalog/skills/redesign-audit/SKILL.md +117 -0
  156. package/catalog/skills/redesign-audit/VALIDATION.json +12 -0
  157. package/catalog/skills/redesign-audit/skill.yaml +9 -0
  158. package/catalog/skills/security-audit/EXAMPLES.md +79 -0
  159. package/catalog/skills/security-audit/SKILL.md +91 -0
  160. package/catalog/skills/security-audit/TROUBLESHOOTING.md +46 -0
  161. package/catalog/skills/security-audit/VALIDATION.json +11 -0
  162. package/catalog/skills/security-audit/skill.yaml +14 -0
  163. package/catalog/skills/soft-design/EXAMPLES.md +51 -0
  164. package/catalog/skills/soft-design/SKILL.md +108 -0
  165. package/catalog/skills/soft-design/VALIDATION.json +12 -0
  166. package/catalog/skills/soft-design/skill.yaml +10 -0
  167. package/catalog/skills/state-management/EXAMPLES.md +56 -0
  168. package/catalog/skills/state-management/SKILL.md +168 -0
  169. package/catalog/skills/state-management/TROUBLESHOOTING.md +18 -0
  170. package/catalog/skills/state-management/VALIDATION.json +11 -0
  171. package/catalog/skills/state-management/skill.yaml +14 -0
  172. package/catalog/skills/subagent-orchestrator/SKILL.md +117 -0
  173. package/catalog/skills/subagent-orchestrator/VALIDATION.json +12 -0
  174. package/catalog/skills/subagent-orchestrator/skill.yaml +9 -0
  175. package/catalog/skills/system-design/EXAMPLES.md +75 -0
  176. package/catalog/skills/system-design/SKILL.md +419 -0
  177. package/catalog/skills/system-design/TROUBLESHOOTING.md +19 -0
  178. package/catalog/skills/system-design/VALIDATION.json +12 -0
  179. package/catalog/skills/system-design/skill.yaml +14 -0
  180. package/catalog/skills/terraform/EXAMPLES.md +74 -0
  181. package/catalog/skills/terraform/SKILL.md +55 -0
  182. package/catalog/skills/terraform/TROUBLESHOOTING.md +53 -0
  183. package/catalog/skills/terraform/VALIDATION.json +11 -0
  184. package/catalog/skills/terraform/skill.yaml +14 -0
  185. package/catalog/skills/testing/EXAMPLES.md +122 -0
  186. package/catalog/skills/testing/SKILL.md +70 -0
  187. package/catalog/skills/testing/TROUBLESHOOTING.md +18 -0
  188. package/catalog/skills/testing/VALIDATION.json +11 -0
  189. package/catalog/skills/testing/skill.yaml +14 -0
  190. package/catalog/skills/typescript/EXAMPLES.md +64 -0
  191. package/catalog/skills/typescript/SKILL.md +112 -0
  192. package/catalog/skills/typescript/TROUBLESHOOTING.md +19 -0
  193. package/catalog/skills/typescript/VALIDATION.json +12 -0
  194. package/catalog/skills/typescript/skill.yaml +14 -0
  195. package/catalog/skills/ui-design/EXAMPLES.md +21 -0
  196. package/catalog/skills/ui-design/SKILL.md +124 -0
  197. package/catalog/skills/ui-design/TROUBLESHOOTING.md +19 -0
  198. package/catalog/skills/ui-design/VALIDATION.json +12 -0
  199. package/catalog/skills/ui-design/skill.yaml +16 -0
  200. package/catalog/skills/ui-ux-pro/EXAMPLES.md +62 -0
  201. package/catalog/skills/ui-ux-pro/SKILL.md +418 -0
  202. package/catalog/skills/ui-ux-pro/TROUBLESHOOTING.md +19 -0
  203. package/catalog/skills/ui-ux-pro/VALIDATION.json +12 -0
  204. package/catalog/skills/ui-ux-pro/skill.yaml +14 -0
  205. package/catalog/skills/ux-design/EXAMPLES.md +36 -0
  206. package/catalog/skills/ux-design/SKILL.md +116 -0
  207. package/catalog/skills/ux-design/TROUBLESHOOTING.md +19 -0
  208. package/catalog/skills/ux-design/VALIDATION.json +12 -0
  209. package/catalog/skills/ux-design/skill.yaml +16 -0
  210. package/catalog/skills/vercel-optimize/SKILL.md +83 -0
  211. package/catalog/skills/vercel-optimize/VALIDATION.json +12 -0
  212. package/catalog/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
  213. package/catalog/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
  214. package/catalog/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
  215. package/catalog/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
  216. package/catalog/skills/vercel-optimize/skill.yaml +15 -0
  217. package/catalog/skills/web-accessibility/EXAMPLES.md +39 -0
  218. package/catalog/skills/web-accessibility/SKILL.md +151 -0
  219. package/catalog/skills/web-accessibility/TROUBLESHOOTING.md +19 -0
  220. package/catalog/skills/web-accessibility/VALIDATION.json +12 -0
  221. package/catalog/skills/web-accessibility/skill.yaml +14 -0
  222. package/package.json +5 -2
  223. package/.agents/core/skills/security/security.md +0 -106
@@ -30,7 +30,7 @@ Activate whenever writing authentication, authorization, session management, dat
30
30
 
31
31
  #### 1. Injection (SQL, NoSQL, Command)
32
32
 
33
- - Always use parameterized queries — never concatenate user input into SQL or shell commands.
33
+ - Always use parameterized queries - never concatenate user input into SQL or shell commands.
34
34
  - Use ORMs (Prisma, Drizzle, SQLAlchemy) with strict schema validation.
35
35
  - Validate and sanitize all user input before processing.
36
36
 
@@ -79,14 +79,20 @@ When building AI workflows, tools, or MCP servers:
79
79
  - Never allow untrusted content to override system instructions or tool execution permissions.
80
80
  2. **Tool Execution Boundaries**:
81
81
  - Destructive operations (database drops, file deletions, payment triggers) MUST require explicit user confirmation.
82
- - Restrict file system tools to the workspace root — block directory traversal (`../`).
82
+ - Restrict file system tools to the workspace root - block directory traversal (`../`).
83
83
  3. **Secret Masking & Output Sanitization**:
84
84
  - Scrub API keys (`sk-...`, `Bearer ...`), tokens, and credentials before writing to agent logs or step summaries.
85
+ 4. **Sandbox Execution & Write Isolation (Supply-Chain Defense)**:
86
+ - Target code is inspected strictly read-only; never execute target-controlled builds or tests with write access to the repository root.
87
+ - Restrict process write boundaries strictly to an isolated temporary `scratch/` directory.
88
+ - Enforce zero outbound external network access during security audits to prevent secret exfiltration via malicious scripts or dependencies.
89
+ - Promote verified non-secret results to retained `artifacts/` only via trusted parent-side inspection code.
85
90
 
86
91
  ---
87
92
 
88
93
  ## Code Examples
89
94
 
95
+
90
96
  ### Timing-Safe Secret Verification
91
97
 
92
98
  ```javascript
@@ -102,28 +108,50 @@ export function verifyWebhookSignature(payload, signature, secret) {
102
108
  }
103
109
  ```
104
110
 
105
- ### Safe SSRF Prevention Wrapper
111
+ ### SSRF Prevention Requirements (OWASP Compliant)
106
112
 
107
- ```typescript
108
- import dns from 'node:dns/promises';
113
+ Per [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html), naive application-level DNS pre-checks followed by standard `fetch(url)` are fundamentally flawed due to DNS rebinding (TOCTOU) and unvalidated HTTP 3xx redirects.
114
+
115
+ #### Mandatory Architectural Controls
116
+
117
+ 1. **Network-Layer Defense (Primary)**: For user-supplied arbitrary webhooks or URLs, route all outbound traffic through an isolated egress forward proxy (e.g., Smokescreen, Envoy, Squid) configured with firewall-level IP filters blocking RFC 1918, RFC 6598, link-local (`169.254.169.254`), loopback, and IPv6 local addresses at the socket handshake level.
118
+ 2. **Positive Destination Allowlist**: If fetching from known external partners, validate destination hostname against a strict positive allowlist.
119
+ 3. **Disable Automatic Redirects**: Always set `redirect: 'error'` or `'manual'`. Never follow HTTP redirects automatically without re-validating the target URL against allowlist rules.
120
+ 4. **Protocol & Credential Restrictions**: Enforce `https:` exclusively; reject embedded credentials (`user:pass@host`) and non-standard ports.
109
121
 
110
- export async function validateSafeUrl(urlString: string): Promise<URL> {
122
+ ```typescript
123
+ /**
124
+ * Verified Allowlist-based HTTP Client (OWASP SSRF Prevention)
125
+ * Enforces HTTPS, strict destination allowlist, and rejects HTTP redirects.
126
+ */
127
+ export async function fetchFromAllowlist(
128
+ urlString: string,
129
+ allowedHostnames: ReadonlySet<string>,
130
+ options: RequestInit = {}
131
+ ): Promise<Response> {
111
132
  const parsed = new URL(urlString);
133
+
134
+ // 1. Enforce HTTPS only
112
135
  if (parsed.protocol !== 'https:') {
113
- throw new Error('Only HTTPS protocol is permitted');
136
+ throw new Error(`SSRF blocked: protocol "${parsed.protocol}" is not permitted; HTTPS required`);
114
137
  }
115
138
 
116
- const { address } = await dns.lookup(parsed.hostname);
117
- if (
118
- address.startsWith('127.') ||
119
- address.startsWith('10.') ||
120
- address.startsWith('192.168.') ||
121
- address === '169.254.169.254'
122
- ) {
123
- throw new Error('Access to private/metadata IP addresses is blocked');
139
+ // 2. Reject credentials in URL
140
+ if (parsed.username || parsed.password) {
141
+ throw new Error('SSRF blocked: URL credentials (user:password@host) are prohibited');
124
142
  }
125
143
 
126
- return parsed;
144
+ // 3. Strict positive destination allowlist (prevents internal network probing)
145
+ const normalizedHost = parsed.hostname.toLowerCase();
146
+ if (!allowedHostnames.has(normalizedHost)) {
147
+ throw new Error(`SSRF blocked: destination host "${normalizedHost}" is not in the approved allowlist`);
148
+ }
149
+
150
+ // 4. Disable automatic redirects to prevent redirection to private IPs or metadata endpoints
151
+ return fetch(urlString, {
152
+ ...options,
153
+ redirect: 'error'
154
+ });
127
155
  }
128
156
  ```
129
157
 
@@ -156,115 +184,6 @@ export async function validateSafeUrl(urlString: string): Promise<URL> {
156
184
  - Pairs with `system-design` to mandate secure network boundaries and authorization layers.
157
185
 
158
186
 
159
- <!-- Source: security.md -->
160
-
161
- # Application Security — Best Practices
162
-
163
- ## OWASP Top 10
164
-
165
- ### 1. Injection (SQL, NoSQL, Command)
166
-
167
- - **Always use parameterized queries** — never concatenate user input into SQL
168
- - Use ORM (Prisma, SQLAlchemy, TypeORM) — they parameterize by default
169
- - Validate and sanitize all user input
170
-
171
- ### 2. Broken Authentication
172
-
173
- - Use bcrypt/argon2 for password hashing (cost factor ≥ 12)
174
- - JWT: short-lived access tokens (15min), refresh tokens (7 days)
175
- - Rate limit login attempts
176
- - Implement account lockout after N failed attempts
177
- - MFA for sensitive operations
178
-
179
- ### 3. Sensitive Data Exposure
180
-
181
- - HTTPS everywhere — redirect HTTP to HTTPS
182
- - Encrypt sensitive data at rest (AES-256)
183
- - Never log passwords, tokens, or PII
184
- - Use environment variables for secrets
185
-
186
- ### 4. XML/XXE
187
-
188
- - Disable external entity processing
189
- - Use JSON instead of XML where possible
190
-
191
- ### 5. Broken Access Control
192
-
193
- - Default deny — explicitly grant access
194
- - RBAC (Role-Based Access Control) or ABAC (Attribute-Based)
195
- - Check authorization on every request, not just UI
196
- - Don't rely on client-side validation for security
197
-
198
- ### 6. Security Misconfiguration
199
-
200
- - Remove default credentials
201
- - Disable debug mode in production
202
- - Security headers (see below)
203
- - Keep dependencies updated
204
-
205
- ### 7. XSS (Cross-Site Scripting)
206
-
207
- - Escape all output by default
208
- - Content-Security-Policy header
209
- - HttpOnly + Secure + SameSite cookies
210
- - Use framework's built-in XSS protection
211
-
212
- ### 8. Insecure Deserialization
213
-
214
- - Validate and schema-check all input (Zod, Pydantic, class-validator)
215
- - Don't deserialize untrusted data
216
-
217
- ### 9. Insufficient Logging
218
-
219
- - Log all authentication events
220
- - Log authorization failures
221
- - Log input validation failures
222
- - Include request ID for tracing
223
-
224
- ### 10. SSRF (Server-Side Request Forgery)
225
-
226
- - Validate and allowlist URLs
227
- - Don't let users control server-side HTTP requests
228
-
229
- ## Security Headers
230
-
231
- ```
232
- Content-Security-Policy: default-src 'self'
233
- X-Content-Type-Options: nosniff
234
- X-Frame-Options: DENY
235
- Strict-Transport-Security: max-age=31536000; includeSubDomains
236
- Referrer-Policy: strict-origin-when-cross-origin
237
- Permissions-Policy: camera=(), microphone=(), geolocation=()
238
- ```
239
-
240
- ## Authentication Patterns
241
-
242
- ### JWT Flow
243
-
244
- ```
245
- Login → Access Token (15min) + Refresh Token (7d, HttpOnly cookie)
246
- Request → Authorization: Bearer <access_token>
247
- Expired → POST /auth/refresh (sends refresh cookie) → new access token
248
- ```
249
-
250
- ### OAuth2 Flow
251
-
252
- ```
253
- Redirect → Provider (Google, GitHub) → Callback → Create/link user → JWT
254
- ```
255
-
256
- ## Checklist Before Deploy
257
-
258
- - [ ] All secrets in environment variables
259
- - [ ] HTTPS enabled
260
- - [ ] Security headers configured
261
- - [ ] Input validation on all endpoints
262
- - [ ] Rate limiting enabled
263
- - [ ] CORS configured (not `*`)
264
- - [ ] Error messages don't leak internals
265
- - [ ] Dependency audit (`npm audit`, `pip audit`)
266
- - [ ] Logging for security events
267
-
268
187
  <!-- Source: EXAMPLES.md -->
269
188
 
270
189
  # Application Security Examples — Anti-patterns vs ContextOS Standard
@@ -26,7 +26,7 @@ const https = require('https');
26
26
  const crypto = require('crypto');
27
27
  const { execFileSync } = require('child_process');
28
28
  const { ProjectMutationLock } = require('./filesystem/project-lock.js');
29
- const JournaledTransaction = require('./filesystem/journaled-transaction.js');
29
+ const { JournaledTransaction } = require('./filesystem/journaled-transaction.js');
30
30
  const {
31
31
  calculateTreeDigest,
32
32
  validateArchiveEntry,
@@ -284,11 +284,49 @@ function isValidNpmPackage(name) {
284
284
  return /^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+$/.test(name);
285
285
  }
286
286
 
287
- function parseRef(ref) {
287
+ function parseRef(ref, projectRoot = null) {
288
288
  if (typeof ref !== 'string' || !ref.trim()) {
289
289
  throw new Error('A plugin reference is required');
290
290
  }
291
291
 
292
+ // 1. Built-in package catalog skills take strict precedence for catalog names.
293
+ // Consumer project local files can never spoof or hijack package catalog skills.
294
+ const pkgCatalogPath = path.join(path.resolve(__dirname, '..'), 'catalog', 'skills', ref);
295
+ if (fs.existsSync(pkgCatalogPath) && fs.existsSync(path.join(pkgCatalogPath, 'SKILL.md'))) {
296
+ return {
297
+ type: 'catalog',
298
+ name: ref,
299
+ path: pkgCatalogPath,
300
+ isPinned: true,
301
+ raw: ref,
302
+ };
303
+ }
304
+
305
+ // 2. Explicit local references (must start with 'local:', './', '../', or 'file:')
306
+ if (ref.startsWith('local:') || ref.startsWith('./') || ref.startsWith('../') || ref.startsWith('file:')) {
307
+ const rawTarget = ref.startsWith('local:')
308
+ ? ref.slice(6)
309
+ : (ref.startsWith('file:') ? ref.slice(5) : ref);
310
+
311
+ const baseRoot = projectRoot || process.cwd();
312
+ let localDir = path.resolve(baseRoot, rawTarget);
313
+ if (!fs.existsSync(localDir)) {
314
+ localDir = path.join(baseRoot, 'catalog', 'skills', rawTarget);
315
+ }
316
+
317
+ if (fs.existsSync(localDir) && fs.existsSync(path.join(localDir, 'SKILL.md'))) {
318
+ const skillName = path.basename(localDir);
319
+ return {
320
+ type: 'catalog',
321
+ name: skillName,
322
+ path: localDir,
323
+ isPinned: true,
324
+ raw: ref,
325
+ };
326
+ }
327
+ throw new Error(`Local skill not found at: '${ref}'`);
328
+ }
329
+
292
330
  // Scoped packages contain a slash but are npm packages, not GitHub refs.
293
331
  if (ref.startsWith('@')) {
294
332
  if (!isValidNpmPackage(ref)) {
@@ -529,6 +567,62 @@ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = f
529
567
  return sha256;
530
568
  }
531
569
 
570
+ // ── Catalog installer ─────────────────────────────────────────────────────────
571
+ function installFromCatalog(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
572
+ const sourceDir = descriptor.path;
573
+ const targetDir = path.join(PLUGINS_DIR, skillName);
574
+ const relTargetDir = toPosix(path.relative(ROOT, targetDir));
575
+
576
+ if (dryRun) {
577
+ console.log(c.dim(` [DRY-RUN] Would copy catalog skill from: ${sourceDir}`));
578
+ return 'sha256:0000000000000000000000000000000000000000000000000000000000000000';
579
+ }
580
+
581
+ function copyRecursive(src, dest, relDest) {
582
+ if (!fs.existsSync(dest)) {
583
+ fs.mkdirSync(dest, { recursive: true });
584
+ }
585
+ const entries = fs.readdirSync(src, { withFileTypes: true });
586
+ for (const entry of entries) {
587
+ const srcPath = path.join(src, entry.name);
588
+ const destPath = path.join(dest, entry.name);
589
+ const relPath = toPosix(path.join(relDest, entry.name));
590
+
591
+ if (entry.isDirectory()) {
592
+ copyRecursive(srcPath, destPath, relPath);
593
+ } else {
594
+ const content = fs.readFileSync(srcPath, 'utf8');
595
+ if (tx) {
596
+ tx.stageWrite(relPath, content);
597
+ } else {
598
+ fs.writeFileSync(destPath, content, 'utf8');
599
+ }
600
+ }
601
+ }
602
+ }
603
+
604
+ copyRecursive(sourceDir, targetDir, relTargetDir);
605
+
606
+ const primarySkillMd = path.join(sourceDir, 'SKILL.md');
607
+ const sha256 = fs.existsSync(primarySkillMd) ? crypto.createHash('sha256').update(fs.readFileSync(primarySkillMd)).digest('hex') : null;
608
+
609
+ const sourceMeta = {
610
+ source: 'catalog',
611
+ name: skillName,
612
+ path: toPosix(path.relative(ROOT, sourceDir)),
613
+ sha256,
614
+ installedAt: new Date().toISOString(),
615
+ };
616
+ if (tx) {
617
+ tx.stageWrite(`${relTargetDir}/.source`, JSON.stringify(sourceMeta, null, 2) + '\n');
618
+ } else {
619
+ fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify(sourceMeta, null, 2) + '\n');
620
+ }
621
+
622
+ console.log(c.green(` ✓ Installed from built-in catalog: ${skillName}`));
623
+ return sha256;
624
+ }
625
+
532
626
  // ── Derive skill name from ref ────────────────────────────────────────────────
533
627
  function deriveSkillName(ref) {
534
628
  // username/repo/path/to/my-skill → my-skill
@@ -570,7 +664,7 @@ async function add(ref, options = {}) {
570
664
  }
571
665
 
572
666
  // Supply-chain source pinning check (Wave 6)
573
- if (!dryRun) {
667
+ if (!dryRun && descriptor.type !== 'catalog') {
574
668
  const pinning = validatePluginPinning(
575
669
  {
576
670
  type: descriptor.type,
@@ -637,7 +731,9 @@ async function add(ref, options = {}) {
637
731
  lockToken = projectLock.acquire({ command: 'skill:add' });
638
732
 
639
733
  let installedSha256 = null;
640
- if (descriptor.type === 'github') {
734
+ if (descriptor.type === 'catalog') {
735
+ installedSha256 = installFromCatalog(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
736
+ } else if (descriptor.type === 'github') {
641
737
  installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
642
738
  } else {
643
739
  installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
@@ -901,17 +997,18 @@ async function search(query) {
901
997
  * Adapters call this instead of reading CORE_SKILLS directly.
902
998
  */
903
999
  function collectAllSkillDirs(targetRoot) {
1000
+ const isTargetExplicit = Boolean(targetRoot);
904
1001
  const root = targetRoot || process.cwd();
905
1002
  const agentsDir = path.join(root, '.agents');
906
1003
  const localCore = path.join(agentsDir, 'core', 'skills');
907
1004
  const localPlugins = path.join(agentsDir, 'plugins');
908
1005
 
909
- const coreDir = fs.existsSync(localCore) ? localCore : CORE_SKILLS;
910
- const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : PLUGINS_DIR;
1006
+ const coreDir = fs.existsSync(localCore) ? localCore : (isTargetExplicit ? null : CORE_SKILLS);
1007
+ const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : (isTargetExplicit ? null : PLUGINS_DIR);
911
1008
  const dirs = [];
912
1009
 
913
1010
  // Core skills
914
- if (fs.existsSync(coreDir)) {
1011
+ if (coreDir && fs.existsSync(coreDir)) {
915
1012
  for (const name of fs.readdirSync(coreDir)) {
916
1013
  const d = path.join(coreDir, name);
917
1014
  if (fs.statSync(d).isDirectory()) dirs.push(d);
@@ -919,7 +1016,7 @@ function collectAllSkillDirs(targetRoot) {
919
1016
  }
920
1017
 
921
1018
  // Plugin skills (supports both standalone skill dirs and plugin bundles with skills/)
922
- if (fs.existsSync(pluginsDir)) {
1019
+ if (pluginsDir && fs.existsSync(pluginsDir)) {
923
1020
  for (const name of fs.readdirSync(pluginsDir)) {
924
1021
  const d = path.join(pluginsDir, name);
925
1022
  if (!fs.statSync(d).isDirectory()) continue;
@@ -298,14 +298,26 @@ function validateProfile(profile, registry = null) {
298
298
  }
299
299
 
300
300
  /**
301
- * Lists all available project profiles defined in core/profiles.
301
+ * Lists all available project profiles defined in core/profiles and optionally consumer project.
302
+ * Searches built-in profiles in packageRoot, and if projectRoot is supplied, searches
303
+ * consumer project .agents/profiles, .agents/core/profiles, and profiles directories.
302
304
  *
305
+ * @param {string|null} [projectRoot=null] - Optional consumer project root
303
306
  * @returns {ProfileConfig[]} Array of profile configurations
304
307
  */
305
- function listProfiles() {
308
+ function listProfiles(projectRoot = null) {
306
309
  const profiles = [];
307
310
  const searchDirs = [PROFILES_DIR, path.join(AGENTS_DIR, 'catalog', 'presets')];
308
311
 
312
+ if (projectRoot) {
313
+ const absConsumer = path.resolve(projectRoot);
314
+ searchDirs.push(
315
+ path.join(absConsumer, '.agents', 'profiles'),
316
+ path.join(absConsumer, '.agents', 'core', 'profiles'),
317
+ path.join(absConsumer, 'profiles')
318
+ );
319
+ }
320
+
309
321
  for (const dir of searchDirs) {
310
322
  if (!fs.existsSync(dir)) continue;
311
323
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
@@ -314,22 +326,31 @@ function listProfiles() {
314
326
  const parsed = parseYamlProfile(content);
315
327
  if (!parsed.id) parsed.id = path.basename(file, path.extname(file));
316
328
  if (!parsed.name) parsed.name = parsed.id;
317
- profiles.push(parsed);
329
+
330
+ // Consumer profiles can override or extend built-in profiles; deduplicate by id
331
+ const normId = parsed.id.toLowerCase();
332
+ const existingIdx = profiles.findIndex(p => p.id.toLowerCase() === normId);
333
+ if (existingIdx !== -1) {
334
+ profiles[existingIdx] = parsed;
335
+ } else {
336
+ profiles.push(parsed);
337
+ }
318
338
  }
319
339
  }
320
340
  return profiles;
321
341
  }
322
342
 
323
343
  /**
324
- * Finds a profile by its slug ID or title.
344
+ * Finds a profile by its slug ID or title across package and consumer roots.
325
345
  *
326
346
  * @param {string} name - Profile identifier or name
347
+ * @param {string|null} [projectRoot=null] - Optional consumer project root
327
348
  * @returns {ProfileConfig|null} Found profile or null
328
349
  */
329
- function getProfile(name) {
350
+ function getProfile(name, projectRoot = null) {
330
351
  if (!name) return null;
331
352
  const clean = name.toLowerCase().trim();
332
- const all = listProfiles();
353
+ const all = listProfiles(projectRoot);
333
354
  return all.find(p => p.id.toLowerCase() === clean || p.name.toLowerCase() === clean) || null;
334
355
  }
335
356
 
@@ -398,7 +419,7 @@ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null)
398
419
  }
399
420
 
400
421
  if (matchedProfileName) {
401
- const overrideProf = getProfile(matchedProfileName);
422
+ const overrideProf = getProfile(matchedProfileName, projectDir);
402
423
  if (overrideProf) {
403
424
  return {
404
425
  ...overrideProf,
@@ -424,9 +445,9 @@ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null)
424
445
  * @returns {Object} Updated profile lock data
425
446
  */
426
447
  function applyProfile(profileName, projectDir = process.cwd(), options = {}) {
427
- const profile = getProfile(profileName);
448
+ const profile = getProfile(profileName, projectDir);
428
449
  if (!profile) {
429
- throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
450
+ throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles(projectDir).map(p => p.id).join(', ')}`);
430
451
  }
431
452
 
432
453
  validateProfile(profile);
@@ -563,9 +584,9 @@ function removeActiveProfile(projectDir = process.cwd(), options = {}) {
563
584
  * @returns {Object} Explanation structure
564
585
  */
565
586
  function explainProfile(profileName, projectDir = process.cwd()) {
566
- const profile = getProfile(profileName);
587
+ const profile = getProfile(profileName, projectDir);
567
588
  if (!profile) {
568
- throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
589
+ throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles(projectDir).map(p => p.id).join(', ')}`);
569
590
  }
570
591
 
571
592
  return {
@@ -23,15 +23,15 @@ const { WorkspaceGraphBuilder } = require('../workspace/workspace-graph');
23
23
  // Prompt budget limits (Section 14.4)
24
24
  const BUDGET_TIERS = {
25
25
  BOOTSTRAP: 1200, // always-on bootstrap budget
26
- SKILL_SUMMARY: 150, // single skill summary limit
27
- SKILL_BODY: 1200, // single skill body limit
28
- ROUTINE: 2000, // routine context limit
29
- STANDARD: 4000, // standard normal compiled context limit
30
- HIGH: 7500, // high-risk compiled context limit
31
- DESTRUCTIVE: 10000, // destructive compiled context limit
26
+ SKILL_SUMMARY: 300, // single skill summary limit
27
+ SKILL_BODY: 10000, // single skill body limit (supports rich code examples and rules)
28
+ ROUTINE: 16000, // routine context limit
29
+ STANDARD: 32000, // standard normal compiled context limit (supports 8-10 full skills)
30
+ HIGH: 64000, // high-risk compiled context limit
31
+ DESTRUCTIVE: 128000, // destructive compiled context limit
32
32
  };
33
33
 
34
- const DEFAULT_CONTEXT_BUDGET_TOKENS = 8000;
34
+ const DEFAULT_CONTEXT_BUDGET_TOKENS = 64000;
35
35
 
36
36
  // Risk-based workflows (Section 14.3)
37
37
  const WORKFLOW_TEMPLATES = {
@@ -641,10 +641,74 @@ function checkRegistryV2() {
641
641
  }
642
642
  }
643
643
 
644
+ // ═════════════════════════════════════════════════════════════════════════════
645
+ // CHECK 13 — Catalog Skills Validation (Optional, gated by --catalog)
646
+ // ═════════════════════════════════════════════════════════════════════════════
647
+ function checkCatalogSkills() {
648
+ const catalogSkillsDir = path.join(ROOT, 'catalog', 'skills');
649
+ if (!fs.existsSync(catalogSkillsDir)) {
650
+ error(`[catalog] Catalog skills directory not found: ${catalogSkillsDir}`);
651
+ return;
652
+ }
653
+
654
+ let validated = 0;
655
+ const entries = fs.readdirSync(catalogSkillsDir);
656
+
657
+ for (const name of entries) {
658
+ const dir = path.join(catalogSkillsDir, name);
659
+ if (!fs.statSync(dir).isDirectory()) continue;
660
+
661
+ const skillMd = path.join(dir, 'SKILL.md');
662
+ const yaml = path.join(dir, 'skill.yaml');
663
+ const validationJson = path.join(dir, 'VALIDATION.json');
664
+
665
+ if (!fs.existsSync(skillMd)) {
666
+ error(`[catalog] ${name}: missing SKILL.md`);
667
+ continue;
668
+ }
669
+
670
+ const content = fs.readFileSync(skillMd, 'utf8');
671
+ const fm = parseFrontmatter(content);
672
+ if (!fm || fm.malformed) {
673
+ error(`[catalog] ${name}: malformed YAML frontmatter in SKILL.md`);
674
+ } else {
675
+ if (!yamlField(fm.raw, 'name')) {
676
+ error(`[catalog] ${name}: missing 'name' in SKILL.md frontmatter`);
677
+ }
678
+ if (!yamlField(fm.raw, 'description')) {
679
+ error(`[catalog] ${name}: missing 'description' in SKILL.md frontmatter`);
680
+ }
681
+ }
682
+
683
+ if (!fs.existsSync(yaml)) {
684
+ error(`[catalog] ${name}: missing skill.yaml`);
685
+ } else {
686
+ const yamlText = fs.readFileSync(yaml, 'utf8');
687
+ if (!yamlField(yamlText, 'name') && !yamlField(yamlText, 'id')) {
688
+ error(`[catalog] ${name}: missing 'name' or 'id' in skill.yaml`);
689
+ }
690
+ }
691
+
692
+ if (fs.existsSync(validationJson)) {
693
+ try {
694
+ JSON.parse(fs.readFileSync(validationJson, 'utf8'));
695
+ } catch (err) {
696
+ error(`[catalog] ${name}: invalid JSON in VALIDATION.json: ${err.message}`);
697
+ }
698
+ }
699
+
700
+ validated++;
701
+ }
702
+
703
+ info(`[catalog] ${validated} catalog skills validated (frontmatter, skill.yaml, validation metadata)`);
704
+ }
705
+
644
706
  // ═════════════════════════════════════════════════════════════════════════════
645
707
  // MAIN
646
708
  // ═════════════════════════════════════════════════════════════════════════════
647
- function run() {
709
+ function run(options = {}) {
710
+ const checkCatalog = Boolean(options.checkCatalog || process.argv.includes('--catalog'));
711
+
648
712
  console.log(c.cyan('\nContextOS Validator — scanning skills...\n'));
649
713
 
650
714
  if (!fs.existsSync(CORE_SKILLS)) {
@@ -670,6 +734,10 @@ function run() {
670
734
  checkProfilesIntegrity(sourceSkills);
671
735
  checkRegistryV2();
672
736
 
737
+ if (checkCatalog) {
738
+ checkCatalogSkills();
739
+ }
740
+
673
741
  const passed = printReport();
674
742
  process.exit(passed ? 0 : 1);
675
743
  }