webspresso 0.0.95 → 0.0.96

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 (73) hide show
  1. package/.agents/ADMIN.md +225 -0
  2. package/.agents/AGENTS.md +72 -0
  3. package/.agents/ARCHITECTURE.md +119 -0
  4. package/.agents/AUTH.md +103 -0
  5. package/.agents/CLI.md +115 -0
  6. package/.agents/COMPRESSION.md +59 -0
  7. package/.agents/CONVENTIONS.md +46 -0
  8. package/.agents/EMAIL.md +141 -0
  9. package/.agents/ERRORS.md +201 -0
  10. package/.agents/MCP.md +103 -0
  11. package/.agents/ORM.md +230 -0
  12. package/.agents/PLUGINS.md +419 -0
  13. package/.agents/QUEUE.md +113 -0
  14. package/.agents/REALTIME.md +213 -0
  15. package/.agents/ROUTING.md +292 -0
  16. package/.agents/SERVICES.md +341 -0
  17. package/.agents/SHUTDOWN.md +67 -0
  18. package/.agents/STREAMING.md +73 -0
  19. package/.agents/WORKFLOW.md +27 -0
  20. package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
  21. package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
  22. package/.cursorrules +14 -0
  23. package/AGENTS.md +175 -0
  24. package/CLAUDE.md +54 -0
  25. package/README.md +18 -14
  26. package/bin/commands/agents-init.js +169 -0
  27. package/bin/commands/api.js +114 -48
  28. package/bin/commands/doctor.js +35 -0
  29. package/bin/commands/new.js +13 -2
  30. package/bin/commands/page.js +75 -89
  31. package/bin/commands/service.js +77 -0
  32. package/bin/webspresso.js +2 -2
  33. package/core/compileSchema.js +1 -1
  34. package/core/errors/middleware.js +22 -2
  35. package/core/errors/normalize.js +29 -2
  36. package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
  37. package/index.d.ts +306 -0
  38. package/index.js +35 -1
  39. package/package.json +5 -3
  40. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
  41. package/plugins/csrf/index.js +3 -2
  42. package/plugins/csv/engine.js +447 -0
  43. package/plugins/csv/index.js +94 -0
  44. package/plugins/csv/middleware.js +80 -0
  45. package/plugins/csv/services.js +138 -0
  46. package/plugins/file-manager/admin-component.js +613 -0
  47. package/plugins/file-manager/api-handlers.js +261 -0
  48. package/plugins/file-manager/core.js +601 -0
  49. package/plugins/file-manager/index.js +139 -0
  50. package/plugins/file-manager/security.js +207 -0
  51. package/plugins/index.js +8 -0
  52. package/plugins/xlsx/engine.js +397 -0
  53. package/plugins/xlsx/index.js +91 -0
  54. package/plugins/xlsx/middleware.js +87 -0
  55. package/plugins/xlsx/services.js +118 -0
  56. package/src/api/api-loader.js +233 -0
  57. package/src/api/define-api.js +35 -0
  58. package/src/discovery/file-route-parser.js +201 -0
  59. package/src/discovery/scan-routes.js +265 -0
  60. package/src/file-router.js +6 -0
  61. package/src/modules/define-module.js +99 -0
  62. package/src/modules/module-discovery.js +117 -0
  63. package/src/pages/define-page.js +36 -0
  64. package/src/pages/page-loader.js +214 -0
  65. package/src/routing/mount-discovered-routes.js +78 -0
  66. package/src/routing/route-table.js +191 -0
  67. package/src/server.js +112 -15
  68. package/src/services/builtins/file-manager.js +178 -0
  69. package/src/services/index.js +2 -0
  70. package/bin/commands/skill.js +0 -187
  71. package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
  72. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
  73. package/templates/skills/webspresso-usage/SKILL.md +0 -48
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Webspresso File Manager Plugin
3
+ * Filesystem-first media library, directory browser and asset picker for Admin Panel.
4
+ * @module plugins/file-manager
5
+ */
6
+
7
+ const path = require('path');
8
+ const { createFileManagerApiHandlers } = require('./api-handlers');
9
+ const { generateFileManagerComponent } = require('./admin-component');
10
+ const { createFileManagerServices } = require('../../src/services/builtins/file-manager');
11
+ const core = require('./core');
12
+ const security = require('./security');
13
+
14
+ /**
15
+ * @param {Object} [options]
16
+ * @param {string} [options.baseDir='./public/uploads'] - Filesystem directory to manage
17
+ * @param {string} [options.publicBasePath='/uploads'] - URL prefix for stored files
18
+ * @param {string} [options.apiPrefix='/files'] - API subpath under Admin API
19
+ * @param {string} [options.pageTitle='Dosya Yöneticisi'] - Title of admin page
20
+ * @param {string} [options.menuLabel='Dosya Yöneticisi'] - Sidebar menu label
21
+ * @param {number} [options.menuOrder=50] - Sidebar menu ordering
22
+ * @param {number} [options.maxUploadBytes=10485760] - Max upload size in bytes (10MB default)
23
+ * @param {number|null} [options.maxTotalStorageBytes] - Storage quota limit in bytes (optional)
24
+ * @param {string[]|null} [options.allowedExtensions] - Whitelist of allowed extensions
25
+ * @param {string[]|null} [options.allowedMimeTypes] - Whitelist of allowed MIME types
26
+ * @param {Record<string, string[]>|null} [options.permissions] - RBAC permissions map
27
+ * @param {Object} [options.security] - Security configurations
28
+ * @param {boolean} [options.security.validateMagicBytes=true] - Validate file signatures
29
+ * @param {boolean} [options.security.sanitizeSvg=true] - Sanitize SVG XSS payloads
30
+ * @param {boolean} [options.includePage=true] - Whether to register admin menu item & page
31
+ */
32
+ function fileManagerPlugin(options = {}) {
33
+ const {
34
+ baseDir = path.join(process.cwd(), 'public', 'uploads'),
35
+ publicBasePath = '/uploads',
36
+ apiPrefix = '/files',
37
+ pageTitle = 'Dosya Yöneticisi',
38
+ menuLabel = 'Dosya Yöneticisi',
39
+ menuOrder = 50,
40
+ maxUploadBytes = 10 * 1024 * 1024,
41
+ maxTotalStorageBytes = null,
42
+ allowedExtensions = null,
43
+ allowedMimeTypes = null,
44
+ permissions = null,
45
+ security: securityOpts = { validateMagicBytes: true, sanitizeSvg: true },
46
+ includePage = true,
47
+ } = options;
48
+
49
+ const resolvedBaseDir = path.resolve(baseDir);
50
+
51
+ const handlers = createFileManagerApiHandlers({
52
+ baseDir: resolvedBaseDir,
53
+ publicBasePath,
54
+ maxUploadBytes,
55
+ maxTotalStorageBytes,
56
+ allowedExtensions,
57
+ allowedMimeTypes,
58
+ permissions,
59
+ security: securityOpts,
60
+ });
61
+
62
+ return {
63
+ name: 'file-manager',
64
+ version: '1.0.0',
65
+ description: 'Filesystem-first media library, directory explorer & asset picker',
66
+ dependencies: { 'admin-panel': '*' },
67
+
68
+ api: {
69
+ ...core,
70
+ security,
71
+ },
72
+
73
+ register(ctx) {
74
+ // Register built-in services in app service registry if available
75
+ if (ctx.app && ctx.app.serviceRegistry) {
76
+ const services = createFileManagerServices({
77
+ baseDir: resolvedBaseDir,
78
+ publicBasePath,
79
+ });
80
+ for (const [name, def] of Object.entries(services)) {
81
+ if (!ctx.app.serviceRegistry.has(name)) {
82
+ ctx.app.serviceRegistry.register(name, def);
83
+ }
84
+ }
85
+ }
86
+ },
87
+
88
+ onRoutesReady(ctx) {
89
+ const adminApi = ctx.usePlugin('admin-panel');
90
+ if (!adminApi) {
91
+ console.warn('[file-manager] admin-panel plugin not found, skipping Admin UI registration');
92
+ return;
93
+ }
94
+
95
+ adminApi.registerModule({
96
+ id: 'files',
97
+ pages: includePage
98
+ ? [{
99
+ id: 'files',
100
+ title: pageTitle,
101
+ path: '/files',
102
+ icon: 'folder',
103
+ component: generateFileManagerComponent({
104
+ apiPrefix,
105
+ publicBasePath,
106
+ }),
107
+ }]
108
+ : [],
109
+
110
+ menu: includePage
111
+ ? [{
112
+ id: 'files',
113
+ label: menuLabel,
114
+ path: '/files',
115
+ icon: 'folder',
116
+ order: menuOrder,
117
+ }]
118
+ : [],
119
+
120
+ api: {
121
+ prefix: apiPrefix,
122
+ routes: [
123
+ { method: 'get', path: '', handler: handlers.list },
124
+ { method: 'post', path: '/mkdir', handler: handlers.mkdir },
125
+ { method: 'post', path: '/rename', handler: handlers.rename },
126
+ { method: 'post', path: '/move', handler: handlers.move },
127
+ { method: 'post', path: '/delete', handler: handlers.delete },
128
+ { method: 'post', path: '/upload', handler: handlers.upload },
129
+ ],
130
+ },
131
+ });
132
+ },
133
+ };
134
+ }
135
+
136
+ module.exports = fileManagerPlugin;
137
+ module.exports.fileManagerPlugin = fileManagerPlugin;
138
+ module.exports.core = core;
139
+ module.exports.security = security;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * File Manager Security Subsystem — Magic byte verification, dangerous extension blocking & SVG sanitization.
3
+ * Zero external dependencies, ultra-high performance O(1) buffer checks.
4
+ * @module plugins/file-manager/security
5
+ */
6
+
7
+ const { SecurityError, ValidationError } = require('../../core/errors');
8
+
9
+ /**
10
+ * Universal list of dangerous/executable extensions (lowercase without dot)
11
+ */
12
+ const DANGEROUS_EXTENSIONS = new Set([
13
+ // Scripts & Executables
14
+ 'php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', 'phar',
15
+ 'exe', 'dll', 'so', 'dylib', 'bin', 'elf',
16
+ 'sh', 'bash', 'zsh', 'csh', 'ksh', 'fish', 'bat', 'cmd', 'ps1', 'psm1',
17
+ 'vbs', 'vbe', 'wsf', 'wsh', 'scr', 'cpl', 'msc', 'hta',
18
+ 'jar', 'war', 'ear', 'class',
19
+ 'jsp', 'jspx', 'jsw', 'jsv', 'jspf',
20
+ 'asp', 'aspx', 'axd', 'asx', 'ashx', 'asmx',
21
+ 'cgi', 'pl', 'pm', 'py', 'pyc', 'pyo', 'rb',
22
+ // System & Config files
23
+ 'htaccess', 'htpasswd', 'ini', 'env', 'config', 'conf', 'passwd', 'shadow',
24
+ ]);
25
+
26
+ /**
27
+ * Known magic numbers table for high-speed signature verification.
28
+ * Only validates when extension matches known binary format.
29
+ */
30
+ const MAGIC_NUMBERS = {
31
+ // JPEG / JPG
32
+ jpg: (buf) => buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff,
33
+ jpeg: (buf) => buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff,
34
+
35
+ // PNG
36
+ png: (buf) =>
37
+ buf.length >= 8 &&
38
+ buf[0] === 0x89 &&
39
+ buf[1] === 0x50 &&
40
+ buf[2] === 0x4e &&
41
+ buf[3] === 0x47 &&
42
+ buf[4] === 0x0d &&
43
+ buf[5] === 0x0a &&
44
+ buf[6] === 0x1a &&
45
+ buf[7] === 0x0a,
46
+
47
+ // GIF
48
+ gif: (buf) =>
49
+ buf.length >= 6 &&
50
+ buf[0] === 0x47 &&
51
+ buf[1] === 0x49 &&
52
+ buf[2] === 0x46 &&
53
+ buf[3] === 0x38 &&
54
+ (buf[4] === 0x37 || buf[4] === 0x39) &&
55
+ buf[5] === 0x61,
56
+
57
+ // WebP: RIFF at 0..3 and WEBP at 8..11
58
+ webp: (buf) =>
59
+ buf.length >= 12 &&
60
+ buf[0] === 0x52 &&
61
+ buf[1] === 0x49 &&
62
+ buf[2] === 0x46 &&
63
+ buf[3] === 0x46 &&
64
+ buf[8] === 0x57 &&
65
+ buf[9] === 0x45 &&
66
+ buf[10] === 0x42 &&
67
+ buf[11] === 0x50,
68
+
69
+ // PDF: %PDF-
70
+ pdf: (buf) =>
71
+ buf.length >= 5 &&
72
+ buf[0] === 0x25 &&
73
+ buf[1] === 0x50 &&
74
+ buf[2] === 0x44 &&
75
+ buf[3] === 0x46 &&
76
+ buf[4] === 0x2d,
77
+
78
+ // ZIP (and DOCX/XLSX/PPTX)
79
+ zip: (buf) =>
80
+ buf.length >= 4 &&
81
+ buf[0] === 0x50 &&
82
+ buf[1] === 0x4b &&
83
+ (buf[2] === 0x03 || buf[2] === 0x05 || buf[2] === 0x07) &&
84
+ (buf[3] === 0x04 || buf[3] === 0x06 || buf[3] === 0x08),
85
+ docx: (buf) => MAGIC_NUMBERS.zip(buf),
86
+ xlsx: (buf) => MAGIC_NUMBERS.zip(buf),
87
+ pptx: (buf) => MAGIC_NUMBERS.zip(buf),
88
+
89
+ // MP3: ID3 or frame sync 0xFFE / 0xFFF
90
+ mp3: (buf) =>
91
+ (buf.length >= 3 && buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) ||
92
+ (buf.length >= 2 && buf[0] === 0xff && (buf[1] & 0xe0) === 0xe0),
93
+
94
+ // MP4: ....ftyp at bytes 4..7
95
+ mp4: (buf) =>
96
+ buf.length >= 8 &&
97
+ buf[4] === 0x66 &&
98
+ buf[5] === 0x74 &&
99
+ buf[6] === 0x79 &&
100
+ buf[7] === 0x70,
101
+ };
102
+
103
+ /**
104
+ * Validate that a filename contains no dangerous extensions or double-extension tricks.
105
+ * @param {string} filename
106
+ * @returns {string} Clean lowercase extension
107
+ */
108
+ function validateSafeExtension(filename) {
109
+ if (!filename || typeof filename !== 'string') {
110
+ throw new ValidationError('Filename is required');
111
+ }
112
+
113
+ const cleanName = filename.toLowerCase().trim();
114
+ const parts = cleanName.split('.').filter(Boolean);
115
+
116
+ if (parts.length === 0) {
117
+ throw new ValidationError('Filename must have a valid extension');
118
+ }
119
+
120
+ // Check all dotted segments against dangerous extensions to block double extensions (e.g. payload.php.jpg)
121
+ for (let i = 1; i < parts.length; i++) {
122
+ const segment = parts[i];
123
+ if (DANGEROUS_EXTENSIONS.has(segment)) {
124
+ throw new SecurityError(
125
+ `Dangerous extension or double-extension detected: .${segment}`,
126
+ 400
127
+ );
128
+ }
129
+ }
130
+
131
+ const finalExt = parts[parts.length - 1];
132
+ if (DANGEROUS_EXTENSIONS.has(finalExt)) {
133
+ throw new SecurityError(`File type .${finalExt} is forbidden for upload`, 403);
134
+ }
135
+
136
+ return finalExt;
137
+ }
138
+
139
+ /**
140
+ * Verify that the buffer's magic bytes match the claimed extension.
141
+ * O(1) time complexity, minimal memory overhead.
142
+ * @param {Buffer} buffer
143
+ * @param {string} ext
144
+ * @returns {boolean}
145
+ */
146
+ function validateMagicBytes(buffer, ext) {
147
+ if (!buffer || !Buffer.isBuffer(buffer)) {
148
+ throw new ValidationError('Valid buffer is required for magic byte check');
149
+ }
150
+
151
+ const cleanExt = (ext || '').replace(/^\./, '').toLowerCase();
152
+ const validator = MAGIC_NUMBERS[cleanExt];
153
+
154
+ if (validator) {
155
+ const isValid = validator(buffer);
156
+ if (!isValid) {
157
+ throw new SecurityError(
158
+ `File signature mismatch: file content does not match expected .${cleanExt} format`,
159
+ 400
160
+ );
161
+ }
162
+ }
163
+
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Sanitize SVG content against Stored XSS attacks.
169
+ * Strips <script>, <foreignObject>, <iframe>, <embed>, <object> and on* event handlers.
170
+ * @param {Buffer|string} content
171
+ * @returns {Buffer} Sanitized SVG buffer
172
+ */
173
+ function sanitizeSvgContent(content) {
174
+ const str = Buffer.isBuffer(content) ? content.toString('utf8') : String(content);
175
+
176
+ // Quick check: if no dangerous keywords, return original buffer directly for maximum speed
177
+ if (!/<(script|foreignobject|iframe|embed|object|meta|link|style)/i.test(str) &&
178
+ !/\s+on[a-z]+\s*=/i.test(str) &&
179
+ !/javascript:/i.test(str) &&
180
+ !/data:text\/html/i.test(str)) {
181
+ return Buffer.isBuffer(content) ? content : Buffer.from(str, 'utf8');
182
+ }
183
+
184
+ let sanitized = str
185
+ // Remove script tags and content
186
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi, '')
187
+ // Remove dangerous containers
188
+ .replace(/<foreignObject\b[^<]*(?:(?!<\/foreignObject>)<[^<]*)*<\/foreignObject\s*>/gi, '')
189
+ .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe\s*>/gi, '')
190
+ .replace(/<embed\b[^>]*>/gi, '')
191
+ .replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object\s*>/gi, '')
192
+ // Remove inline event handlers (onload, onclick, onerror, onmouseover, etc.)
193
+ .replace(/\s+on[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
194
+ // Remove javascript: and data:text/html URIs in href and xlink:href
195
+ .replace(/(?:href|xlink:href)\s*=\s*(?:"\s*javascript:[^"]*"|'\s*javascript:[^']*')/gi, 'href=""')
196
+ .replace(/(?:href|xlink:href)\s*=\s*(?:"\s*data:text\/html[^"]*"|'\s*data:text\/html[^']*')/gi, 'href=""');
197
+
198
+ return Buffer.from(sanitized, 'utf8');
199
+ }
200
+
201
+ module.exports = {
202
+ DANGEROUS_EXTENSIONS,
203
+ MAGIC_NUMBERS,
204
+ validateSafeExtension,
205
+ validateMagicBytes,
206
+ sanitizeSvgContent,
207
+ };
package/plugins/index.js CHANGED
@@ -41,6 +41,9 @@ const {
41
41
  } = require('./realtime');
42
42
  const { mcpPlugin, createMcpServer, McpServer } = require('./mcp');
43
43
  const polarPlugin = require('./polar');
44
+ const fileManagerPlugin = require('./file-manager');
45
+ const { xlsxPlugin, xlsx } = require('./xlsx');
46
+ const { csvPlugin, csv } = require('./csv');
44
47
 
45
48
  module.exports = {
46
49
  sitemapPlugin,
@@ -83,6 +86,11 @@ module.exports = {
83
86
  createMcpServer,
84
87
  McpServer,
85
88
  polarPlugin,
89
+ fileManagerPlugin,
90
+ xlsxPlugin,
91
+ xlsx,
92
+ csvPlugin,
93
+ csv,
86
94
  };
87
95
 
88
96