0
# Security and Configuration
1
2
Advanced security features and configuration options for customizing request behavior, handling redirects, and preventing SSRF attacks through DNS resolution validation and other security measures.
3
4
## Capabilities
5
6
### Configuration Options
7
8
Comprehensive configuration interface for customizing link preview behavior, security settings, and request parameters.
9
10
```typescript { .api }
11
interface ILinkPreviewOptions {
12
/** Custom HTTP headers for request */
13
headers?: Record<string, string>;
14
/** Property type for image meta tags (default: "og") */
15
imagesPropertyType?: string;
16
/** Proxy URL to prefix to the target URL */
17
proxyUrl?: string;
18
/** Request timeout in milliseconds (default: 3000) */
19
timeout?: number;
20
/** Redirect handling strategy (default: "error") */
21
followRedirects?: "follow" | "error" | "manual";
22
/** Function to resolve DNS for SSRF protection */
23
resolveDNSHost?: (url: string) => Promise<string>;
24
/** Function to validate redirects (required with followRedirects: "manual") */
25
handleRedirects?: (baseURL: string, forwardedURL: string) => boolean;
26
/** Callback to modify response object */
27
onResponse?: (response: ILinkPreviewResponse, doc: cheerio.Root, url?: URL) => ILinkPreviewResponse;
28
}
29
```
30
31
### Custom Headers
32
33
Configure custom HTTP headers for requests to handle authentication, user agents, and other requirements.
34
35
**Basic Header Configuration:**
36
37
```typescript
38
import { getLinkPreview } from "link-preview-js";
39
40
// Custom User-Agent
41
const preview = await getLinkPreview("https://example.com", {
42
headers: {
43
"User-Agent": "MyBot/1.0 (+https://mysite.com/bot)"
44
}
45
});
46
47
// Multiple headers
48
const customPreview = await getLinkPreview("https://example.com", {
49
headers: {
50
"User-Agent": "Mozilla/5.0 (compatible; MyBot/1.0)",
51
"Accept-Language": "en-US,en;q=0.9",
52
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
53
"Authorization": "Bearer your-token-here"
54
}
55
});
56
```
57
58
**Common Header Use Cases:**
59
60
```typescript
61
// Bot identification (recommended for crawlers)
62
const botHeaders = {
63
"User-Agent": "GoogleBot/2.1 (+http://www.google.com/bot.html)"
64
};
65
66
// Language-specific content
67
const localizedHeaders = {
68
"Accept-Language": "fr-FR,fr;q=0.8,en;q=0.6"
69
};
70
71
// CORS proxy requirements
72
const corsHeaders = {
73
"Origin": "https://myapp.com",
74
"X-Requested-With": "XMLHttpRequest"
75
};
76
```
77
78
### Timeout Configuration
79
80
Configure request timeout to prevent hanging requests and control response times.
81
82
```typescript
83
import { getLinkPreview } from "link-preview-js";
84
85
// Short timeout for fast responses
86
const quickPreview = await getLinkPreview("https://fast-site.com", {
87
timeout: 1000 // 1 second
88
});
89
90
// Longer timeout for slow sites
91
const patientPreview = await getLinkPreview("https://slow-site.com", {
92
timeout: 10000 // 10 seconds
93
});
94
95
// Handle timeout errors
96
try {
97
await getLinkPreview("https://very-slow-site.com", { timeout: 2000 });
98
} catch (error) {
99
if (error.message === "Request timeout") {
100
console.log("Site is taking too long to respond");
101
}
102
}
103
```
104
105
### Image Property Type Configuration
106
107
Control which meta tag properties are used for image extraction.
108
109
```typescript
110
import { getLinkPreview } from "link-preview-js";
111
112
// Use only OpenGraph images
113
const ogPreview = await getLinkPreview("https://example.com", {
114
imagesPropertyType: "og"
115
});
116
117
// Use Twitter Card images
118
const twitterPreview = await getLinkPreview("https://example.com", {
119
imagesPropertyType: "twitter"
120
});
121
122
// Use custom property type
123
const customPreview = await getLinkPreview("https://example.com", {
124
imagesPropertyType: "custom"
125
});
126
// Looks for meta[property='custom:image'] or meta[name='custom:image']
127
```
128
129
### Proxy Configuration
130
131
Configure proxy URLs for CORS bypass or corporate network requirements.
132
133
```typescript
134
import { getLinkPreview } from "link-preview-js";
135
136
// Using CORS proxy
137
const proxyPreview = await getLinkPreview("https://target-site.com", {
138
proxyUrl: "https://cors-anywhere.herokuapp.com/",
139
headers: {
140
"Origin": "https://myapp.com"
141
}
142
});
143
144
// Corporate proxy
145
const corpPreview = await getLinkPreview("https://external-site.com", {
146
proxyUrl: "http://corporate-proxy.company.com:8080/",
147
headers: {
148
"Proxy-Authorization": "Basic " + btoa("username:password")
149
}
150
});
151
```
152
153
### Redirect Handling
154
155
Configure how the library handles HTTP redirects with three strategies: follow, error, or manual.
156
157
**Follow Redirects (Caution Required):**
158
159
```typescript
160
import { getLinkPreview } from "link-preview-js";
161
162
// Automatically follow redirects (security risk)
163
const preview = await getLinkPreview("http://shorturl.com/abc123", {
164
followRedirects: "follow"
165
});
166
```
167
168
**Error on Redirects (Default - Secure):**
169
170
```typescript
171
// Default behavior - throw error on redirects
172
try {
173
const preview = await getLinkPreview("http://redirect-site.com", {
174
followRedirects: "error" // Default value
175
});
176
} catch (error) {
177
console.log("Redirect detected and blocked for security");
178
}
179
```
180
181
**Manual Redirect Handling (Recommended):**
182
183
```typescript
184
import { getLinkPreview } from "link-preview-js";
185
186
// Manual redirect validation
187
const preview = await getLinkPreview("https://short.ly/abc123", {
188
followRedirects: "manual",
189
handleRedirects: (baseURL: string, forwardedURL: string) => {
190
const baseUrl = new URL(baseURL);
191
const forwardedUrl = new URL(forwardedURL);
192
193
// Allow same-domain redirects
194
if (forwardedUrl.hostname === baseUrl.hostname) {
195
return true;
196
}
197
198
// Allow HTTP to HTTPS upgrades
199
if (baseUrl.protocol === "http:" &&
200
forwardedUrl.protocol === "https:" &&
201
forwardedUrl.hostname === baseUrl.hostname) {
202
return true;
203
}
204
205
// Allow www subdomain redirects
206
if (forwardedUrl.hostname === "www." + baseUrl.hostname ||
207
"www." + forwardedUrl.hostname === baseUrl.hostname) {
208
return true;
209
}
210
211
// Block all other redirects
212
return false;
213
}
214
});
215
```
216
217
### SSRF Protection
218
219
Server-Side Request Forgery (SSRF) protection through DNS resolution validation and IP address filtering.
220
221
**Built-in IP Filtering:**
222
223
The library automatically blocks requests to private network ranges:
224
- Loopback: 127.0.0.0/8
225
- Private Class A: 10.0.0.0/8
226
- Private Class B: 172.16.0.0/12
227
- Private Class C: 192.168.0.0/16
228
- Link-local: 169.254.0.0/16
229
- Documentation ranges: 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24
230
- Carrier-Grade NAT: 100.64.0.0/10
231
232
**DNS Resolution Validation:**
233
234
```typescript
235
import { getLinkPreview } from "link-preview-js";
236
import dns from "dns";
237
238
// DNS resolution for SSRF protection
239
const securePreview = await getLinkPreview("https://suspicious-domain.com", {
240
resolveDNSHost: async (url: string) => {
241
return new Promise((resolve, reject) => {
242
const hostname = new URL(url).hostname;
243
dns.lookup(hostname, (err, address, family) => {
244
if (err) {
245
reject(err);
246
return;
247
}
248
249
// Additional custom validation
250
if (address.startsWith("127.") || address.startsWith("192.168.")) {
251
reject(new Error("Blocked private IP address"));
252
return;
253
}
254
255
resolve(address);
256
});
257
});
258
}
259
});
260
```
261
262
**Advanced SSRF Protection:**
263
264
```typescript
265
import { getLinkPreview } from "link-preview-js";
266
import dns from "dns/promises";
267
268
async function advancedDnsCheck(url: string): Promise<string> {
269
const hostname = new URL(url).hostname;
270
271
try {
272
// Resolve all A records
273
const addresses = await dns.resolve4(hostname);
274
275
// Check each resolved address
276
for (const address of addresses) {
277
const parts = address.split('.').map(Number);
278
279
// Block RFC 1918 private networks
280
if (parts[0] === 10) throw new Error("Private network blocked");
281
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) {
282
throw new Error("Private network blocked");
283
}
284
if (parts[0] === 192 && parts[1] === 168) {
285
throw new Error("Private network blocked");
286
}
287
288
// Block loopback
289
if (parts[0] === 127) throw new Error("Loopback blocked");
290
291
// Block multicast and reserved ranges
292
if (parts[0] >= 224) throw new Error("Reserved range blocked");
293
}
294
295
return addresses[0];
296
} catch (error) {
297
throw new Error(`DNS resolution failed: ${error.message}`);
298
}
299
}
300
301
const securePreview = await getLinkPreview("https://example.com", {
302
resolveDNSHost: advancedDnsCheck
303
});
304
```
305
306
### Response Processing Callbacks
307
308
Customize response objects with the `onResponse` callback for site-specific handling or data enhancement.
309
310
```typescript
311
import { getLinkPreview } from "link-preview-js";
312
313
const customPreview = await getLinkPreview("https://example.com", {
314
onResponse: (response, doc, url) => {
315
// Site-specific customizations
316
if (url?.hostname === "github.com") {
317
// GitHub-specific enhancements
318
const repoInfo = doc('meta[name="octolytics-dimension-repository_nwo"]').attr('content');
319
if (repoInfo) {
320
response.siteName = `GitHub - ${repoInfo}`;
321
}
322
}
323
324
// Fallback description from first paragraph
325
if (!response.description) {
326
const firstParagraph = doc('p').first().text();
327
if (firstParagraph) {
328
response.description = firstParagraph.substring(0, 200) + "...";
329
}
330
}
331
332
// Clean up image URLs
333
response.images = response.images.filter(img =>
334
img && !img.includes('tracking') && !img.includes('analytics')
335
);
336
337
// Add custom metadata
338
const customData = doc('meta[name="custom-data"]').attr('content');
339
if (customData) {
340
(response as any).customField = customData;
341
}
342
343
return response;
344
}
345
});
346
```
347
348
**Structured Response Processing:**
349
350
```typescript
351
// Type-safe response enhancement
352
interface EnhancedResponse extends ILinkPreviewResponse {
353
readingTime?: number;
354
language?: string;
355
keywords?: string[];
356
}
357
358
const enhancedPreview = await getLinkPreview("https://blog.example.com", {
359
onResponse: (response, doc, url): EnhancedResponse => {
360
const enhanced = response as EnhancedResponse;
361
362
// Calculate reading time
363
const textContent = doc('article, main, .content').text();
364
const wordCount = textContent.split(/\s+/).length;
365
enhanced.readingTime = Math.ceil(wordCount / 200); // Avg 200 WPM
366
367
// Extract language
368
enhanced.language = doc('html').attr('lang') || 'en';
369
370
// Extract keywords
371
const keywordsMeta = doc('meta[name="keywords"]').attr('content');
372
enhanced.keywords = keywordsMeta ? keywordsMeta.split(',').map(k => k.trim()) : [];
373
374
return enhanced;
375
}
376
}) as EnhancedResponse;
377
378
console.log(`Reading time: ${enhancedPreview.readingTime} minutes`);
379
```