@fazetitans/fscopy 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -237,6 +237,8 @@ The transform function receives:
237
237
 
238
238
  Return the transformed document, or `null` to skip it.
239
239
 
240
+ > **Security Warning**: The `--transform` option executes arbitrary code from the specified file. Only use transform files from trusted sources. Never run transforms from untrusted or unverified files as they have full access to your system.
241
+
240
242
  ### Webhook Notifications
241
243
 
242
244
  Get notified when transfers complete (success or failure):
@@ -393,6 +395,14 @@ fscopy --init config.json
393
395
  4. **Retry logic** - Automatic retry with exponential backoff on failures
394
396
  5. **Subcollection discovery** - Uses `listCollections()` to find nested data
395
397
 
398
+ ## Security
399
+
400
+ - **Transform files execute arbitrary code** - The `--transform` option uses dynamic imports to load and execute JavaScript/TypeScript files. Only use transform files you have written or thoroughly reviewed. Malicious transform files could access your filesystem, network, or credentials.
401
+
402
+ - **Webhook URLs should use HTTPS** - fscopy warns if you use HTTP webhooks (except localhost). Webhook payloads contain project names and transfer statistics that could be sensitive.
403
+
404
+ - **Credentials via ADC** - fscopy uses Google Application Default Credentials. Ensure you're authenticated with the correct account before running transfers.
405
+
396
406
  ## Notes
397
407
 
398
408
  - **Dry run is ON by default** - Use `-d false` for actual transfer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fazetitans/fscopy",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Fast CLI tool to copy Firestore collections between Firebase projects with filtering, parallel transfers, and subcollection support",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -23,7 +23,7 @@ import { validateConfig } from './config/validator.js';
23
23
  import { defaults } from './config/defaults.js';
24
24
  import { generateConfigFile } from './config/generator.js';
25
25
  import { loadTransferState, saveTransferState, createInitialState, validateStateForResume, deleteTransferState } from './state/index.js';
26
- import { sendWebhook } from './webhook/index.js';
26
+ import { sendWebhook, validateWebhookUrl } from './webhook/index.js';
27
27
  import { countDocuments, transferCollection, clearCollection, deleteOrphanDocuments, processInParallel, getDestCollectionPath, type TransferContext, type CountProgress } from './transfer/index.js';
28
28
  import { runInteractiveMode } from './interactive.js';
29
29
 
@@ -442,6 +442,18 @@ try {
442
442
  process.exit(1);
443
443
  }
444
444
 
445
+ // Validate webhook URL if configured
446
+ if (config.webhook) {
447
+ const webhookValidation = validateWebhookUrl(config.webhook);
448
+ if (!webhookValidation.valid) {
449
+ console.log(`\n❌ ${webhookValidation.warning}`);
450
+ process.exit(1);
451
+ }
452
+ if (webhookValidation.warning) {
453
+ console.log(`\n⚠️ ${webhookValidation.warning}`);
454
+ }
455
+ }
456
+
445
457
  // Skip confirmation in interactive mode (already confirmed by selection)
446
458
  if (!argv.yes && !argv.interactive) {
447
459
  const confirmed = await askConfirmation(config);
@@ -1,8 +1,9 @@
1
1
  export function matchesExcludePattern(path: string, patterns: string[]): boolean {
2
2
  for (const pattern of patterns) {
3
3
  if (pattern.includes('*')) {
4
- // Convert glob pattern to regex
5
- const regex = new RegExp('^' + pattern.replaceAll('*', '.*') + '$');
4
+ // Convert glob pattern to regex (escape special chars first, then convert * to .*)
5
+ const escaped = pattern.replaceAll(/[.+?^${}()|[\]\\]/g, String.raw`\$&`);
6
+ const regex = new RegExp('^' + escaped.replaceAll('*', '.*') + '$');
6
7
  if (regex.test(path)) return true;
7
8
  } else if (path === pattern || path.endsWith('/' + pattern)) {
8
9
  // Exact match or ends with pattern
@@ -22,6 +22,24 @@ export function detectWebhookType(url: string): 'slack' | 'discord' | 'custom' {
22
22
  return 'custom';
23
23
  }
24
24
 
25
+ export function validateWebhookUrl(url: string): { valid: boolean; warning?: string } {
26
+ try {
27
+ const parsed = new URL(url);
28
+ const isLocalhost = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
29
+
30
+ if (parsed.protocol !== 'https:' && !isLocalhost) {
31
+ return {
32
+ valid: true,
33
+ warning: `Webhook URL uses HTTP instead of HTTPS. Data will be sent unencrypted.`,
34
+ };
35
+ }
36
+
37
+ return { valid: true };
38
+ } catch {
39
+ return { valid: false, warning: `Invalid webhook URL: ${url}` };
40
+ }
41
+ }
42
+
25
43
  export function formatSlackPayload(payload: WebhookPayload): Record<string, unknown> {
26
44
  const status = payload.success ? ':white_check_mark: Success' : ':x: Failed';
27
45
  const mode = payload.dryRun ? ' (DRY RUN)' : '';