@shipstatic/types 0.5.3 → 0.6.0

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.d.ts CHANGED
@@ -156,10 +156,12 @@ export interface DomainValidateResponse {
156
156
  * Deployment token for automated deployments
157
157
  */
158
158
  export interface Token {
159
- /** The token hash (not the actual token value) */
159
+ /** 7-char management identifier */
160
160
  readonly token: string;
161
161
  /** The account this token belongs to */
162
162
  readonly account: string;
163
+ /** SHA256 hash of the raw credential (auth lookups only, never exposed to users) */
164
+ readonly hash: string;
163
165
  /** IP address locking for security, null if not locked */
164
166
  readonly ip: string | null;
165
167
  /** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
@@ -173,10 +175,10 @@ export interface Token {
173
175
  }
174
176
  /**
175
177
  * Token as returned by the list endpoint.
176
- * Security-redacted: shows truncated prefix instead of full hash, omits account.
178
+ * Shows 7-char management ID, omits account and hash.
177
179
  */
178
180
  export interface TokenListItem {
179
- /** Truncated token prefix for identification (e.g., "hash12345678...") */
181
+ /** 7-char management identifier (e.g., "a1b2c3d") */
180
182
  readonly token: string;
181
183
  /** Labels for categorization and filtering. Always present, empty array when none. */
182
184
  labels: string[];
@@ -200,8 +202,10 @@ export interface TokenListResponse {
200
202
  * Response for token creation
201
203
  */
202
204
  export interface TokenCreateResponse {
203
- /** The actual token value (only returned on creation) */
205
+ /** 7-char management identifier */
204
206
  token: string;
207
+ /** The raw credential value (shown once at creation, then never again) */
208
+ secret: string;
205
209
  /** Labels for categorization and filtering. Always present, empty array when none. */
206
210
  labels: string[];
207
211
  /** Unix timestamp (seconds) when token expires, null for never */
@@ -341,99 +345,38 @@ export declare class ShipError extends Error {
341
345
  */
342
346
  export declare function isShipError(error: unknown): error is ShipError;
343
347
  /**
344
- * Platform configuration response from API
345
- *
346
- * Contains ONLY dynamic, runtime-specific values (plan-based limits).
347
- * Static constants (MIME types, validation rules) live as exported constants.
348
+ * Dynamic platform configuration returned by the /config endpoint.
349
+ * Contains plan-based limits that vary by account.
348
350
  */
349
351
  export interface ConfigResponse {
350
- /** Maximum individual file size in bytes */
351
352
  maxFileSize: number;
352
- /** Maximum number of files per deployment */
353
353
  maxFilesCount: number;
354
- /** Maximum total deployment size in bytes */
355
354
  maxTotalSize: number;
356
355
  }
357
356
  /**
358
- * Allowed MIME types for static web hosting.
359
- *
360
- * This is a static platform constant, not per-user configuration.
361
- * Safe to share across frontend/backend due to atomic deploys.
362
- *
363
- * Validation rules:
364
- * - Exact match: 'application/json' allows only 'application/json'
365
- * - Prefix match: 'image/' allows all image types (png, jpeg, webp, etc.)
366
- *
367
- * Coverage: 100% of browser-renderable web content
368
- * - Core web (HTML, CSS, JS, WASM)
369
- * - Media (images, audio, video, fonts)
370
- * - Documents (PDF, Markdown, data formats)
371
- * - Modern web (PWA, 3D, structured data)
372
- *
373
- * ============================================================================
374
- * INTENTIONALLY EXCLUDED (Security & Platform Integrity)
375
- * ============================================================================
376
- *
377
- * We are a WEB HOSTING platform, not a file distribution service.
378
- * GitHub Pages-style parity for renderable content, more restrictive for downloads.
379
- *
380
- * 1. EXECUTABLES (Malware Distribution)
381
- * → .exe, .msi, .dmg, .deb, .rpm, .app, .apk, .jar
382
- * → Reason: Direct malware delivery vector
383
- * → Alternative: Use GitHub Releases or dedicated software distribution CDN
357
+ * Blocked file extensions files that cannot be uploaded.
384
358
  *
385
- * 2. ARCHIVES (Piracy & Abuse)
386
- * .zip, .rar, .tar, .gz, .7z, .bz2
387
- * Reason: File sharing abuse, can contain executables, no web rendering
388
- * → Alternative: Use file hosting service (Dropbox, Google Drive) or GitHub Releases
359
+ * We accept any file type by default and derive Content-Type from the
360
+ * extension at serve time (via mime-db in the API worker). Unknown extensions
361
+ * are served as `application/octet-stream` with `X-Content-Type-Options: nosniff`.
389
362
  *
390
- * 3. SERVER-SIDE SCRIPTS (Credential Leakage)
391
- * .php, .asp, .jsp, .cgi
392
- * → Reason: Source code exposure (database passwords, API keys, secrets)
393
- * → Alternative: Static hosting only - use serverless functions for backends
394
- *
395
- * 4. SHELL SCRIPTS (OS Execution)
396
- * → .sh, .bash, .bat, .cmd, .ps1, .vbs
397
- * → Reason: Execute on user's OS outside browser sandbox, social engineering risk
398
- * → Alternative: Embed code examples in HTML <pre><code> or link to GitHub repo
399
- *
400
- * 5. PROGRAMMING LANGUAGE SOURCE (Platform Scope)
401
- * → .py, .rb, .pl, .java, .c, .cpp, .cs, .go, .rs
402
- * → Reason: Not web-renderable, better served by GitHub/GitLab/Bitbucket
403
- * → Alternative: Use GitHub for code hosting, link to repository
404
- *
405
- * 6. OFFICE DOCUMENTS (Macro Malware)
406
- * → .doc, .docx, .xls, .xlsx, .ppt, .pptx
407
- * → Reason: Can contain VBA macros, active exploits in the wild
408
- * → Alternative: Use PDF for documents (fully supported)
409
- *
410
- * 7. GENERIC BINARIES (Unvalidatable)
411
- * → application/octet-stream
412
- * → Reason: Too broad - allows any binary format, cannot moderate effectively
413
- * → Alternative: Use specific MIME types for known formats
414
- *
415
- * ============================================================================
416
- * Security Model:
417
- * - Browser sandbox (JS/WASM execute safely in controlled environment)
418
- * - AI content moderation (scans text/image content for abuse)
419
- * - No server-side execution (static files only)
420
- * - Explicit allowlist (only approved formats, reject unknown)
421
- * ============================================================================
363
+ * The blocklist targets file types that pose direct security risks when hosted:
364
+ * executables, disk images, malware vectors, dangerous scripts, and shortcuts.
422
365
  */
423
- export declare const ALLOWED_MIME_TYPES: readonly ["text/html", "text/css", "text/plain", "text/markdown", "text/xml", "text/csv", "text/tab-separated-values", "text/yaml", "text/vcard", "text/mdx", "text/x-mdx", "text/vtt", "text/srt", "text/calendar", "text/javascript", "text/typescript", "application/x-typescript", "text/tsx", "text/jsx", "text/x-scss", "text/x-sass", "text/less", "text/x-less", "text/stylus", "text/x-vue", "text/x-svelte", "text/x-sql", "text/x-diff", "text/x-patch", "text/x-protobuf", "text/x-ini", "text/x-tex", "text/x-latex", "text/x-bibtex", "text/x-r-markdown", "image/", "audio/", "video/", "font/", "application/javascript", "application/ecmascript", "application/x-javascript", "application/wasm", "application/json", "application/ld+json", "application/geo+json", "application/manifest+json", "application/x-ipynb+json", "application/x-ndjson", "application/ndjson", "text/x-ndjson", "application/jsonl", "text/jsonl", "application/json5", "text/json5", "application/schema+json", "application/source-map", "application/xml", "application/xhtml+xml", "application/rss+xml", "application/atom+xml", "application/feed+json", "application/vnd.google-earth.kml+xml", "application/yaml", "application/toml", "application/pdf", "application/x-subrip", "application/sql", "application/graphql", "application/graphql+json", "application/x-protobuf", "application/x-ini", "application/x-tex", "application/x-bibtex", "model/gltf+json", "model/gltf-binary", "application/mp4", "application/font-woff", "application/font-woff2", "application/x-font-woff", "application/x-woff", "application/vnd.ms-fontobject", "application/x-font-ttf", "application/x-font-truetype", "application/x-font-otf", "application/x-font-opentype"];
366
+ export declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
424
367
  /**
425
- * Check if a MIME type is allowed for upload.
426
- *
427
- * Supports both exact matches and prefix matches:
428
- * - 'application/json' matches 'application/json' exactly
429
- * - 'text/' matches 'text/plain', 'text/html', etc.
368
+ * Check if a filename has a blocked extension.
369
+ * Extracts the extension from the filename and checks against the blocklist.
370
+ * Case-insensitive. Returns false for files without extensions.
430
371
  *
431
372
  * @example
432
- * isAllowedMimeType('text/plain') // true (prefix match)
433
- * isAllowedMimeType('application/json') // true (exact match)
434
- * isAllowedMimeType('application/wasm') // false (not allowed)
373
+ * isBlockedExtension('virus.exe') // true
374
+ * isBlockedExtension('app.dmg') // true
375
+ * isBlockedExtension('style.css') // false
376
+ * isBlockedExtension('data.custom') // false
377
+ * isBlockedExtension('README') // false
435
378
  */
436
- export declare function isAllowedMimeType(mimeType: string): boolean;
379
+ export declare function isBlockedExtension(filename: string): boolean;
437
380
  /**
438
381
  * Simple ping response for health checks
439
382
  */
@@ -784,7 +727,6 @@ export interface ValidationIssue {
784
727
  export interface ValidatableFile {
785
728
  name: string;
786
729
  size: number;
787
- type: string;
788
730
  status?: FileValidationStatusType;
789
731
  statusMessage?: string;
790
732
  }
package/dist/index.js CHANGED
@@ -199,215 +199,55 @@ export function isShipError(error) {
199
199
  error.name === 'ShipError' &&
200
200
  'status' in error);
201
201
  }
202
+ // =============================================================================
203
+ // EXTENSION BLOCKLIST
204
+ // =============================================================================
202
205
  /**
203
- * Allowed MIME types for static web hosting.
204
- *
205
- * This is a static platform constant, not per-user configuration.
206
- * Safe to share across frontend/backend due to atomic deploys.
207
- *
208
- * Validation rules:
209
- * - Exact match: 'application/json' allows only 'application/json'
210
- * - Prefix match: 'image/' allows all image types (png, jpeg, webp, etc.)
211
- *
212
- * Coverage: 100% of browser-renderable web content
213
- * - Core web (HTML, CSS, JS, WASM)
214
- * - Media (images, audio, video, fonts)
215
- * - Documents (PDF, Markdown, data formats)
216
- * - Modern web (PWA, 3D, structured data)
217
- *
218
- * ============================================================================
219
- * INTENTIONALLY EXCLUDED (Security & Platform Integrity)
220
- * ============================================================================
221
- *
222
- * We are a WEB HOSTING platform, not a file distribution service.
223
- * GitHub Pages-style parity for renderable content, more restrictive for downloads.
206
+ * Blocked file extensions files that cannot be uploaded.
224
207
  *
225
- * 1. EXECUTABLES (Malware Distribution)
226
- * .exe, .msi, .dmg, .deb, .rpm, .app, .apk, .jar
227
- * Reason: Direct malware delivery vector
228
- * → Alternative: Use GitHub Releases or dedicated software distribution CDN
208
+ * We accept any file type by default and derive Content-Type from the
209
+ * extension at serve time (via mime-db in the API worker). Unknown extensions
210
+ * are served as `application/octet-stream` with `X-Content-Type-Options: nosniff`.
229
211
  *
230
- * 2. ARCHIVES (Piracy & Abuse)
231
- * .zip, .rar, .tar, .gz, .7z, .bz2
232
- * → Reason: File sharing abuse, can contain executables, no web rendering
233
- * → Alternative: Use file hosting service (Dropbox, Google Drive) or GitHub Releases
234
- *
235
- * 3. SERVER-SIDE SCRIPTS (Credential Leakage)
236
- * → .php, .asp, .jsp, .cgi
237
- * → Reason: Source code exposure (database passwords, API keys, secrets)
238
- * → Alternative: Static hosting only - use serverless functions for backends
239
- *
240
- * 4. SHELL SCRIPTS (OS Execution)
241
- * → .sh, .bash, .bat, .cmd, .ps1, .vbs
242
- * → Reason: Execute on user's OS outside browser sandbox, social engineering risk
243
- * → Alternative: Embed code examples in HTML <pre><code> or link to GitHub repo
244
- *
245
- * 5. PROGRAMMING LANGUAGE SOURCE (Platform Scope)
246
- * → .py, .rb, .pl, .java, .c, .cpp, .cs, .go, .rs
247
- * → Reason: Not web-renderable, better served by GitHub/GitLab/Bitbucket
248
- * → Alternative: Use GitHub for code hosting, link to repository
249
- *
250
- * 6. OFFICE DOCUMENTS (Macro Malware)
251
- * → .doc, .docx, .xls, .xlsx, .ppt, .pptx
252
- * → Reason: Can contain VBA macros, active exploits in the wild
253
- * → Alternative: Use PDF for documents (fully supported)
254
- *
255
- * 7. GENERIC BINARIES (Unvalidatable)
256
- * → application/octet-stream
257
- * → Reason: Too broad - allows any binary format, cannot moderate effectively
258
- * → Alternative: Use specific MIME types for known formats
259
- *
260
- * ============================================================================
261
- * Security Model:
262
- * - Browser sandbox (JS/WASM execute safely in controlled environment)
263
- * - AI content moderation (scans text/image content for abuse)
264
- * - No server-side execution (static files only)
265
- * - Explicit allowlist (only approved formats, reject unknown)
266
- * ============================================================================
212
+ * The blocklist targets file types that pose direct security risks when hosted:
213
+ * executables, disk images, malware vectors, dangerous scripts, and shortcuts.
267
214
  */
268
- export const ALLOWED_MIME_TYPES = [
269
- // =========================================================================
270
- // TEXT CONTENT (explicit list - no prefix matching for security)
271
- // =========================================================================
272
- // Core web documents
273
- 'text/html', // HTML pages
274
- 'text/css', // Stylesheets
275
- 'text/plain', // Plain text (robots.txt, .well-known/*, LICENSE, README.txt)
276
- 'text/markdown', // Markdown files (.md)
277
- 'text/xml', // XML files
278
- // Data formats
279
- 'text/csv', // CSV data files
280
- 'text/tab-separated-values', // TSV data files
281
- 'text/yaml', // YAML config files
282
- 'text/vcard', // VCard contact files (.vcf)
283
- // Modern documentation formats
284
- 'text/mdx', // MDX (Markdown with JSX) - Next.js, Docusaurus, Nextra
285
- 'text/x-mdx', // MDX (alternative MIME type)
286
- // Web-specific formats
287
- 'text/vtt', // WebVTT video subtitles/captions (accessibility)
288
- 'text/srt', // SRT subtitles (SubRip format, legacy video captions)
289
- 'text/calendar', // iCalendar (.ics) event files
290
- // JavaScript (legacy MIME type, still widely used by ~50% of servers)
291
- 'text/javascript',
292
- // Modern web development formats (uncompiled source)
293
- 'text/typescript', // TypeScript source (.ts)
294
- 'application/x-typescript', // TypeScript (alternative MIME type, .d.ts declarations)
295
- 'text/tsx', // TypeScript JSX (.tsx)
296
- 'text/jsx', // React JSX (.jsx)
297
- 'text/x-scss', // SCSS preprocessor
298
- 'text/x-sass', // Sass preprocessor
299
- 'text/less', // Less preprocessor
300
- 'text/x-less', // Less preprocessor (alternative)
301
- 'text/stylus', // Stylus preprocessor
302
- 'text/x-vue', // Vue single-file components (.vue)
303
- 'text/x-svelte', // Svelte components (.svelte)
304
- // Developer documentation formats
305
- 'text/x-sql', // SQL files (database schemas, migrations)
306
- 'text/x-diff', // Diff files (code comparisons, patches)
307
- 'text/x-patch', // Patch files (version upgrades, migrations)
308
- 'text/x-protobuf', // Protocol Buffers text format (gRPC schemas)
309
- 'text/x-ini', // INI configuration files
310
- // Academic/research formats
311
- 'text/x-tex', // LaTeX documents
312
- 'text/x-latex', // LaTeX documents (alternative)
313
- 'text/x-bibtex', // BibTeX citations
314
- 'text/x-r-markdown', // R Markdown (statistical documentation)
315
- // =========================================================================
316
- // MEDIA (prefix matching - covers all common subtypes)
317
- // =========================================================================
318
- // Images: PNG, JPEG, GIF, SVG, WebP, AVIF, HEIC, BMP, TIFF, ICO, etc.
319
- 'image/',
320
- // Audio: MP3, OGG, WAV, WebM, AAC, FLAC, Opus, etc.
321
- 'audio/',
322
- // Video: MP4, WebM, OGG, QuickTime, etc.
323
- 'video/',
324
- // Modern fonts: WOFF2, WOFF, TTF, OTF
325
- 'font/',
326
- // =========================================================================
327
- // CORE WEB APPLICATION TYPES
328
- // =========================================================================
329
- // JavaScript (multiple MIME types for compatibility)
330
- 'application/javascript', // Modern standard (RFC 9239)
331
- 'application/ecmascript', // ECMAScript (legacy but still used)
332
- 'application/x-javascript', // Legacy variant (old CDNs, Apache configs)
333
- // WebAssembly (modern web apps, games, compute-heavy workloads)
334
- 'application/wasm',
335
- // JSON and structured data
336
- 'application/json',
337
- 'application/ld+json', // JSON-LD for structured data / SEO (Schema.org, Open Graph)
338
- 'application/geo+json', // GeoJSON for mapping (Leaflet, Mapbox)
339
- 'application/manifest+json', // PWA web app manifests
340
- 'application/x-ipynb+json', // Jupyter Notebooks (data science, ML tutorials)
341
- // JSON variants (AI/ML, streaming data, configs)
342
- 'application/x-ndjson', // Newline-Delimited JSON (training datasets, logs)
343
- 'application/ndjson', // NDJSON (alternative MIME type)
344
- 'text/x-ndjson', // NDJSON (text variant)
345
- 'application/jsonl', // JSON Lines (Hugging Face, OpenAI fine-tuning)
346
- 'text/jsonl', // JSON Lines (text variant)
347
- 'application/json5', // JSON5 (JSON with comments, trailing commas)
348
- 'text/json5', // JSON5 (text variant)
349
- 'application/schema+json', // JSON Schema (API specs, model definitions)
350
- // Development tools
351
- 'application/source-map', // Source maps (.js.map, .css.map) for debugging
352
- // XML and feeds
353
- 'application/xml',
354
- 'application/xhtml+xml', // XHTML - XML-compliant HTML (legacy sites)
355
- 'application/rss+xml', // RSS feeds (blogs, podcasts)
356
- 'application/atom+xml', // Atom feeds
357
- 'application/feed+json', // JSON Feed (modern RSS alternative)
358
- 'application/vnd.google-earth.kml+xml', // KML for mapping (Google Earth, GIS)
359
- // Configuration formats
360
- 'application/yaml', // YAML configs (static site generators)
361
- 'application/toml', // TOML configs (Cargo, Netlify, Rust projects)
362
- // Documents
363
- 'application/pdf', // PDF documents
364
- // Media metadata
365
- 'application/x-subrip', // SRT subtitles (SubRip format)
366
- // Developer tools and schemas
367
- 'application/sql', // SQL files (database schemas, queries)
368
- 'application/graphql', // GraphQL schemas (API documentation)
369
- 'application/graphql+json', // GraphQL with JSON encoding
370
- 'application/x-protobuf', // Protocol Buffers binary (gRPC)
371
- 'application/x-ini', // INI configuration files
372
- // Academic formats
373
- 'application/x-tex', // LaTeX documents
374
- 'application/x-bibtex', // BibTeX citations
375
- // =========================================================================
376
- // 3D FORMATS (industry standard only)
377
- // =========================================================================
378
- // glTF - Khronos standard for 3D web content
379
- 'model/gltf+json', // glTF JSON format
380
- 'model/gltf-binary', // GLB binary format
381
- // =========================================================================
382
- // LEGACY COMPATIBILITY
383
- // =========================================================================
384
- // Video (some tools detect MP4 as application/mp4)
385
- 'application/mp4',
386
- // Legacy font MIME types (Bootstrap, Font Awesome, IE compatibility)
387
- 'application/font-woff',
388
- 'application/font-woff2',
389
- 'application/x-font-woff',
390
- 'application/x-woff',
391
- 'application/vnd.ms-fontobject', // EOT files (Internet Explorer)
392
- 'application/x-font-ttf',
393
- 'application/x-font-truetype',
394
- 'application/x-font-otf',
395
- 'application/x-font-opentype',
396
- ];
215
+ export const BLOCKED_EXTENSIONS = new Set([
216
+ // Executables
217
+ 'exe', 'msi', 'dll', 'scr', 'bat', 'cmd', 'com', 'pif', 'app', 'deb', 'rpm',
218
+ // Installers
219
+ 'pkg', 'mpkg',
220
+ // Disk images
221
+ 'dmg', 'iso', 'img',
222
+ // Malware vectors
223
+ 'cab', 'cpl', 'chm',
224
+ // Dangerous scripts
225
+ 'ps1', 'vbs', 'vbe', 'ws', 'wsf', 'wsc', 'wsh', 'reg',
226
+ // Java
227
+ 'jar', 'jnlp',
228
+ // Mobile/browser packages
229
+ 'apk', 'crx',
230
+ // Shortcut/link
231
+ 'lnk', 'inf', 'hta',
232
+ ]);
397
233
  /**
398
- * Check if a MIME type is allowed for upload.
399
- *
400
- * Supports both exact matches and prefix matches:
401
- * - 'application/json' matches 'application/json' exactly
402
- * - 'text/' matches 'text/plain', 'text/html', etc.
234
+ * Check if a filename has a blocked extension.
235
+ * Extracts the extension from the filename and checks against the blocklist.
236
+ * Case-insensitive. Returns false for files without extensions.
403
237
  *
404
238
  * @example
405
- * isAllowedMimeType('text/plain') // true (prefix match)
406
- * isAllowedMimeType('application/json') // true (exact match)
407
- * isAllowedMimeType('application/wasm') // false (not allowed)
239
+ * isBlockedExtension('virus.exe') // true
240
+ * isBlockedExtension('app.dmg') // true
241
+ * isBlockedExtension('style.css') // false
242
+ * isBlockedExtension('data.custom') // false
243
+ * isBlockedExtension('README') // false
408
244
  */
409
- export function isAllowedMimeType(mimeType) {
410
- return ALLOWED_MIME_TYPES.some(allowed => mimeType === allowed || mimeType.startsWith(allowed));
245
+ export function isBlockedExtension(filename) {
246
+ const dotIndex = filename.lastIndexOf('.');
247
+ if (dotIndex === -1 || dotIndex === filename.length - 1)
248
+ return false;
249
+ const ext = filename.slice(dotIndex + 1).toLowerCase();
250
+ return BLOCKED_EXTENSIONS.has(ext);
411
251
  }
412
252
  // API Key Configuration
413
253
  export const API_KEY_PREFIX = 'ship-';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Shared types for Shipstatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -182,10 +182,12 @@ export interface DomainValidateResponse {
182
182
  * Deployment token for automated deployments
183
183
  */
184
184
  export interface Token {
185
- /** The token hash (not the actual token value) */
185
+ /** 7-char management identifier */
186
186
  readonly token: string;
187
187
  /** The account this token belongs to */
188
188
  readonly account: string;
189
+ /** SHA256 hash of the raw credential (auth lookups only, never exposed to users) */
190
+ readonly hash: string;
189
191
  /** IP address locking for security, null if not locked */
190
192
  readonly ip: string | null;
191
193
  /** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
@@ -200,10 +202,10 @@ export interface Token {
200
202
 
201
203
  /**
202
204
  * Token as returned by the list endpoint.
203
- * Security-redacted: shows truncated prefix instead of full hash, omits account.
205
+ * Shows 7-char management ID, omits account and hash.
204
206
  */
205
207
  export interface TokenListItem {
206
- /** Truncated token prefix for identification (e.g., "hash12345678...") */
208
+ /** 7-char management identifier (e.g., "a1b2c3d") */
207
209
  readonly token: string;
208
210
  /** Labels for categorization and filtering. Always present, empty array when none. */
209
211
  labels: string[];
@@ -229,8 +231,10 @@ export interface TokenListResponse {
229
231
  * Response for token creation
230
232
  */
231
233
  export interface TokenCreateResponse {
232
- /** The actual token value (only returned on creation) */
234
+ /** 7-char management identifier */
233
235
  token: string;
236
+ /** The raw credential value (shown once at creation, then never again) */
237
+ secret: string;
234
238
  /** Labels for categorization and filtering. Always present, empty array when none. */
235
239
  labels: string[];
236
240
  /** Unix timestamp (seconds) when token expires, null for never */
@@ -494,266 +498,69 @@ export function isShipError(error: unknown): error is ShipError {
494
498
  // =============================================================================
495
499
 
496
500
  /**
497
- * Platform configuration response from API
498
- *
499
- * Contains ONLY dynamic, runtime-specific values (plan-based limits).
500
- * Static constants (MIME types, validation rules) live as exported constants.
501
+ * Dynamic platform configuration returned by the /config endpoint.
502
+ * Contains plan-based limits that vary by account.
501
503
  */
502
504
  export interface ConfigResponse {
503
- /** Maximum individual file size in bytes */
504
505
  maxFileSize: number;
505
- /** Maximum number of files per deployment */
506
506
  maxFilesCount: number;
507
- /** Maximum total deployment size in bytes */
508
507
  maxTotalSize: number;
509
508
  }
510
509
 
510
+ // =============================================================================
511
+ // EXTENSION BLOCKLIST
512
+ // =============================================================================
513
+
511
514
  /**
512
- * Allowed MIME types for static web hosting.
513
- *
514
- * This is a static platform constant, not per-user configuration.
515
- * Safe to share across frontend/backend due to atomic deploys.
516
- *
517
- * Validation rules:
518
- * - Exact match: 'application/json' allows only 'application/json'
519
- * - Prefix match: 'image/' allows all image types (png, jpeg, webp, etc.)
520
- *
521
- * Coverage: 100% of browser-renderable web content
522
- * - Core web (HTML, CSS, JS, WASM)
523
- * - Media (images, audio, video, fonts)
524
- * - Documents (PDF, Markdown, data formats)
525
- * - Modern web (PWA, 3D, structured data)
526
- *
527
- * ============================================================================
528
- * INTENTIONALLY EXCLUDED (Security & Platform Integrity)
529
- * ============================================================================
530
- *
531
- * We are a WEB HOSTING platform, not a file distribution service.
532
- * GitHub Pages-style parity for renderable content, more restrictive for downloads.
515
+ * Blocked file extensions files that cannot be uploaded.
533
516
  *
534
- * 1. EXECUTABLES (Malware Distribution)
535
- * .exe, .msi, .dmg, .deb, .rpm, .app, .apk, .jar
536
- * Reason: Direct malware delivery vector
537
- * → Alternative: Use GitHub Releases or dedicated software distribution CDN
517
+ * We accept any file type by default and derive Content-Type from the
518
+ * extension at serve time (via mime-db in the API worker). Unknown extensions
519
+ * are served as `application/octet-stream` with `X-Content-Type-Options: nosniff`.
538
520
  *
539
- * 2. ARCHIVES (Piracy & Abuse)
540
- * .zip, .rar, .tar, .gz, .7z, .bz2
541
- * → Reason: File sharing abuse, can contain executables, no web rendering
542
- * → Alternative: Use file hosting service (Dropbox, Google Drive) or GitHub Releases
543
- *
544
- * 3. SERVER-SIDE SCRIPTS (Credential Leakage)
545
- * → .php, .asp, .jsp, .cgi
546
- * → Reason: Source code exposure (database passwords, API keys, secrets)
547
- * → Alternative: Static hosting only - use serverless functions for backends
548
- *
549
- * 4. SHELL SCRIPTS (OS Execution)
550
- * → .sh, .bash, .bat, .cmd, .ps1, .vbs
551
- * → Reason: Execute on user's OS outside browser sandbox, social engineering risk
552
- * → Alternative: Embed code examples in HTML <pre><code> or link to GitHub repo
553
- *
554
- * 5. PROGRAMMING LANGUAGE SOURCE (Platform Scope)
555
- * → .py, .rb, .pl, .java, .c, .cpp, .cs, .go, .rs
556
- * → Reason: Not web-renderable, better served by GitHub/GitLab/Bitbucket
557
- * → Alternative: Use GitHub for code hosting, link to repository
558
- *
559
- * 6. OFFICE DOCUMENTS (Macro Malware)
560
- * → .doc, .docx, .xls, .xlsx, .ppt, .pptx
561
- * → Reason: Can contain VBA macros, active exploits in the wild
562
- * Alternative: Use PDF for documents (fully supported)
563
- *
564
- * 7. GENERIC BINARIES (Unvalidatable)
565
- * → application/octet-stream
566
- * → Reason: Too broad - allows any binary format, cannot moderate effectively
567
- * → Alternative: Use specific MIME types for known formats
568
- *
569
- * ============================================================================
570
- * Security Model:
571
- * - Browser sandbox (JS/WASM execute safely in controlled environment)
572
- * - AI content moderation (scans text/image content for abuse)
573
- * - No server-side execution (static files only)
574
- * - Explicit allowlist (only approved formats, reject unknown)
575
- * ============================================================================
576
- */
577
- export const ALLOWED_MIME_TYPES = [
578
- // =========================================================================
579
- // TEXT CONTENT (explicit list - no prefix matching for security)
580
- // =========================================================================
581
-
582
- // Core web documents
583
- 'text/html', // HTML pages
584
- 'text/css', // Stylesheets
585
- 'text/plain', // Plain text (robots.txt, .well-known/*, LICENSE, README.txt)
586
- 'text/markdown', // Markdown files (.md)
587
- 'text/xml', // XML files
588
-
589
- // Data formats
590
- 'text/csv', // CSV data files
591
- 'text/tab-separated-values', // TSV data files
592
- 'text/yaml', // YAML config files
593
- 'text/vcard', // VCard contact files (.vcf)
594
-
595
- // Modern documentation formats
596
- 'text/mdx', // MDX (Markdown with JSX) - Next.js, Docusaurus, Nextra
597
- 'text/x-mdx', // MDX (alternative MIME type)
598
-
599
- // Web-specific formats
600
- 'text/vtt', // WebVTT video subtitles/captions (accessibility)
601
- 'text/srt', // SRT subtitles (SubRip format, legacy video captions)
602
- 'text/calendar', // iCalendar (.ics) event files
603
-
604
- // JavaScript (legacy MIME type, still widely used by ~50% of servers)
605
- 'text/javascript',
606
-
607
- // Modern web development formats (uncompiled source)
608
- 'text/typescript', // TypeScript source (.ts)
609
- 'application/x-typescript', // TypeScript (alternative MIME type, .d.ts declarations)
610
- 'text/tsx', // TypeScript JSX (.tsx)
611
- 'text/jsx', // React JSX (.jsx)
612
- 'text/x-scss', // SCSS preprocessor
613
- 'text/x-sass', // Sass preprocessor
614
- 'text/less', // Less preprocessor
615
- 'text/x-less', // Less preprocessor (alternative)
616
- 'text/stylus', // Stylus preprocessor
617
- 'text/x-vue', // Vue single-file components (.vue)
618
- 'text/x-svelte', // Svelte components (.svelte)
619
-
620
- // Developer documentation formats
621
- 'text/x-sql', // SQL files (database schemas, migrations)
622
- 'text/x-diff', // Diff files (code comparisons, patches)
623
- 'text/x-patch', // Patch files (version upgrades, migrations)
624
- 'text/x-protobuf', // Protocol Buffers text format (gRPC schemas)
625
- 'text/x-ini', // INI configuration files
626
-
627
- // Academic/research formats
628
- 'text/x-tex', // LaTeX documents
629
- 'text/x-latex', // LaTeX documents (alternative)
630
- 'text/x-bibtex', // BibTeX citations
631
- 'text/x-r-markdown', // R Markdown (statistical documentation)
632
-
633
- // =========================================================================
634
- // MEDIA (prefix matching - covers all common subtypes)
635
- // =========================================================================
636
-
637
- // Images: PNG, JPEG, GIF, SVG, WebP, AVIF, HEIC, BMP, TIFF, ICO, etc.
638
- 'image/',
639
-
640
- // Audio: MP3, OGG, WAV, WebM, AAC, FLAC, Opus, etc.
641
- 'audio/',
642
-
643
- // Video: MP4, WebM, OGG, QuickTime, etc.
644
- 'video/',
645
-
646
- // Modern fonts: WOFF2, WOFF, TTF, OTF
647
- 'font/',
648
-
649
- // =========================================================================
650
- // CORE WEB APPLICATION TYPES
651
- // =========================================================================
652
-
653
- // JavaScript (multiple MIME types for compatibility)
654
- 'application/javascript', // Modern standard (RFC 9239)
655
- 'application/ecmascript', // ECMAScript (legacy but still used)
656
- 'application/x-javascript', // Legacy variant (old CDNs, Apache configs)
657
-
658
- // WebAssembly (modern web apps, games, compute-heavy workloads)
659
- 'application/wasm',
660
-
661
- // JSON and structured data
662
- 'application/json',
663
- 'application/ld+json', // JSON-LD for structured data / SEO (Schema.org, Open Graph)
664
- 'application/geo+json', // GeoJSON for mapping (Leaflet, Mapbox)
665
- 'application/manifest+json', // PWA web app manifests
666
- 'application/x-ipynb+json', // Jupyter Notebooks (data science, ML tutorials)
667
-
668
- // JSON variants (AI/ML, streaming data, configs)
669
- 'application/x-ndjson', // Newline-Delimited JSON (training datasets, logs)
670
- 'application/ndjson', // NDJSON (alternative MIME type)
671
- 'text/x-ndjson', // NDJSON (text variant)
672
- 'application/jsonl', // JSON Lines (Hugging Face, OpenAI fine-tuning)
673
- 'text/jsonl', // JSON Lines (text variant)
674
- 'application/json5', // JSON5 (JSON with comments, trailing commas)
675
- 'text/json5', // JSON5 (text variant)
676
- 'application/schema+json', // JSON Schema (API specs, model definitions)
677
-
678
- // Development tools
679
- 'application/source-map', // Source maps (.js.map, .css.map) for debugging
680
-
681
- // XML and feeds
682
- 'application/xml',
683
- 'application/xhtml+xml', // XHTML - XML-compliant HTML (legacy sites)
684
- 'application/rss+xml', // RSS feeds (blogs, podcasts)
685
- 'application/atom+xml', // Atom feeds
686
- 'application/feed+json', // JSON Feed (modern RSS alternative)
687
- 'application/vnd.google-earth.kml+xml', // KML for mapping (Google Earth, GIS)
688
-
689
- // Configuration formats
690
- 'application/yaml', // YAML configs (static site generators)
691
- 'application/toml', // TOML configs (Cargo, Netlify, Rust projects)
692
-
693
- // Documents
694
- 'application/pdf', // PDF documents
695
-
696
- // Media metadata
697
- 'application/x-subrip', // SRT subtitles (SubRip format)
698
-
699
- // Developer tools and schemas
700
- 'application/sql', // SQL files (database schemas, queries)
701
- 'application/graphql', // GraphQL schemas (API documentation)
702
- 'application/graphql+json', // GraphQL with JSON encoding
703
- 'application/x-protobuf', // Protocol Buffers binary (gRPC)
704
- 'application/x-ini', // INI configuration files
705
-
706
- // Academic formats
707
- 'application/x-tex', // LaTeX documents
708
- 'application/x-bibtex', // BibTeX citations
709
-
710
- // =========================================================================
711
- // 3D FORMATS (industry standard only)
712
- // =========================================================================
713
-
714
- // glTF - Khronos standard for 3D web content
715
- 'model/gltf+json', // glTF JSON format
716
- 'model/gltf-binary', // GLB binary format
717
-
718
- // =========================================================================
719
- // LEGACY COMPATIBILITY
720
- // =========================================================================
721
-
722
- // Video (some tools detect MP4 as application/mp4)
723
- 'application/mp4',
724
-
725
- // Legacy font MIME types (Bootstrap, Font Awesome, IE compatibility)
726
- 'application/font-woff',
727
- 'application/font-woff2',
728
- 'application/x-font-woff',
729
- 'application/x-woff',
730
- 'application/vnd.ms-fontobject', // EOT files (Internet Explorer)
731
- 'application/x-font-ttf',
732
- 'application/x-font-truetype',
733
- 'application/x-font-otf',
734
- 'application/x-font-opentype',
735
- ] as const;
736
-
737
- /**
738
- * Check if a MIME type is allowed for upload.
739
- *
740
- * Supports both exact matches and prefix matches:
741
- * - 'application/json' matches 'application/json' exactly
742
- * - 'text/' matches 'text/plain', 'text/html', etc.
521
+ * The blocklist targets file types that pose direct security risks when hosted:
522
+ * executables, disk images, malware vectors, dangerous scripts, and shortcuts.
523
+ */
524
+ export const BLOCKED_EXTENSIONS: ReadonlySet<string> = new Set([
525
+ // Executables
526
+ 'exe', 'msi', 'dll', 'scr', 'bat', 'cmd', 'com', 'pif', 'app', 'deb', 'rpm',
527
+ // Installers
528
+ 'pkg', 'mpkg',
529
+ // Disk images
530
+ 'dmg', 'iso', 'img',
531
+ // Malware vectors
532
+ 'cab', 'cpl', 'chm',
533
+ // Dangerous scripts
534
+ 'ps1', 'vbs', 'vbe', 'ws', 'wsf', 'wsc', 'wsh', 'reg',
535
+ // Java
536
+ 'jar', 'jnlp',
537
+ // Mobile/browser packages
538
+ 'apk', 'crx',
539
+ // Shortcut/link
540
+ 'lnk', 'inf', 'hta',
541
+ ]);
542
+
543
+ /**
544
+ * Check if a filename has a blocked extension.
545
+ * Extracts the extension from the filename and checks against the blocklist.
546
+ * Case-insensitive. Returns false for files without extensions.
743
547
  *
744
548
  * @example
745
- * isAllowedMimeType('text/plain') // true (prefix match)
746
- * isAllowedMimeType('application/json') // true (exact match)
747
- * isAllowedMimeType('application/wasm') // false (not allowed)
748
- */
749
- export function isAllowedMimeType(mimeType: string): boolean {
750
- return ALLOWED_MIME_TYPES.some(allowed =>
751
- mimeType === allowed || mimeType.startsWith(allowed)
752
- );
549
+ * isBlockedExtension('virus.exe') // true
550
+ * isBlockedExtension('app.dmg') // true
551
+ * isBlockedExtension('style.css') // false
552
+ * isBlockedExtension('data.custom') // false
553
+ * isBlockedExtension('README') // false
554
+ */
555
+ export function isBlockedExtension(filename: string): boolean {
556
+ const dotIndex = filename.lastIndexOf('.');
557
+ if (dotIndex === -1 || dotIndex === filename.length - 1) return false;
558
+ const ext = filename.slice(dotIndex + 1).toLowerCase();
559
+ return BLOCKED_EXTENSIONS.has(ext);
753
560
  }
754
561
 
755
562
  // =============================================================================
756
- // COMMON RESPONSE PATTERNS
563
+ // COMMON RESPONSE PATTERNS
757
564
  // =============================================================================
758
565
 
759
566
  /**
@@ -1290,7 +1097,6 @@ export interface ValidationIssue {
1290
1097
  export interface ValidatableFile {
1291
1098
  name: string;
1292
1099
  size: number;
1293
- type: string;
1294
1100
  status?: FileValidationStatusType;
1295
1101
  statusMessage?: string;
1296
1102
  }