@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 +10 -0
- package/package.json +1 -1
- package/src/cli.ts +13 -1
- package/src/utils/patterns.ts +3 -2
- package/src/webhook/index.ts +18 -0
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
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);
|
package/src/utils/patterns.ts
CHANGED
|
@@ -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
|
|
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
|
package/src/webhook/index.ts
CHANGED
|
@@ -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)' : '';
|