or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdlink-preview.mdpre-fetched-content.mdsecurity-configuration.md
tile.json

security-configuration.mddocs/

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

```