or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analysis.mdbackend-api.mdcli.mdindex.mdingestors.mdintegration.mdtesting.mdtypes.md

integration.mddocs/

0

# Platform Integration and Ingestors

1

2

Traffic collection and platform integration capabilities for various environments and programming languages. The integration layer provides flexible ingestors and configuration options for collecting API traffic from diverse infrastructure setups.

3

4

## Capabilities

5

6

### Traffic Collection Parameters

7

8

#### Trace Parameters

9

10

Complete parameter set for collecting and forwarding API traffic to the Metlo backend.

11

12

```typescript { .api }

13

interface TraceParams {

14

/** HTTP request data */

15

request: Request;

16

/** HTTP response data */

17

response: Response;

18

/** Network traffic metadata */

19

meta: Meta;

20

/** Pre-processed security analysis results */

21

processedTraceData?: ProcessedTraceData;

22

/** Whether sensitive data was redacted from the trace */

23

redacted?: boolean;

24

/** Session and authentication metadata */

25

sessionMeta?: SessionMeta;

26

/** Encryption configuration for sensitive fields */

27

encryption?: Encryption;

28

/** Type of analysis requested */

29

analysisType?: AnalysisType;

30

/** GraphQL-specific operation paths */

31

graphqlPaths?: string[];

32

}

33

```

34

35

**Usage Examples:**

36

37

```typescript

38

import { TraceParams, RestMethod, AnalysisType } from "@metlo/common";

39

40

// Collect trace from Express.js middleware

41

function collectExpressTrace(req: any, res: any, responseBody: string): TraceParams {

42

return {

43

request: {

44

url: {

45

host: req.get('host'),

46

path: req.path,

47

parameters: Object.entries(req.query).map(([name, value]) => ({

48

name,

49

value: String(value)

50

}))

51

},

52

headers: Object.entries(req.headers).map(([name, value]) => ({

53

name,

54

value: String(value)

55

})),

56

body: JSON.stringify(req.body),

57

method: req.method as RestMethod

58

},

59

response: {

60

status: res.statusCode,

61

headers: Object.entries(res.getHeaders()).map(([name, value]) => ({

62

name,

63

value: String(value)

64

})),

65

body: responseBody

66

},

67

meta: {

68

incoming: true,

69

source: req.ip || req.connection.remoteAddress,

70

sourcePort: String(req.connection.remotePort),

71

destination: req.connection.localAddress,

72

destinationPort: String(req.connection.localPort)

73

},

74

analysisType: AnalysisType.FULL

75

};

76

}

77

```

78

79

### Network Metadata

80

81

#### Traffic Metadata

82

83

Network-level information about API traffic for security analysis and monitoring.

84

85

```typescript { .api }

86

interface Meta {

87

/** Whether traffic is incoming to the monitored service */

88

incoming: boolean;

89

/** Source IP address */

90

source: string;

91

/** Source port number as string */

92

sourcePort: string;

93

/** Destination IP address */

94

destination: string;

95

/** Destination port number as string */

96

destinationPort: string;

97

/** Original source IP before any proxy/load balancer */

98

originalSource?: string;

99

}

100

```

101

102

**Usage Examples:**

103

104

```typescript

105

import { Meta } from "@metlo/common";

106

107

// Extract metadata from various proxy headers

108

function extractNetworkMeta(req: any): Meta {

109

// Handle X-Forwarded-For and similar proxy headers

110

const forwardedFor = req.headers['x-forwarded-for'];

111

const realIP = req.headers['x-real-ip'];

112

const originalSource = forwardedFor ? forwardedFor.split(',')[0].trim() : realIP;

113

114

return {

115

incoming: true,

116

source: req.connection.remoteAddress || req.ip,

117

sourcePort: String(req.connection.remotePort || 0),

118

destination: req.connection.localAddress,

119

destinationPort: String(req.connection.localPort || req.socket.localPort),

120

originalSource: originalSource || undefined

121

};

122

}

123

124

// Handle load balancer metadata

125

function extractLoadBalancerMeta(headers: Record<string, string>): Partial<Meta> {

126

return {

127

originalSource: headers['x-forwarded-for']?.split(',')[0].trim(),

128

// AWS ALB specific

129

...(headers['x-amzn-trace-id'] && {

130

source: headers['x-forwarded-for']?.split(',')[0].trim()

131

})

132

};

133

}

134

```

135

136

### Session and Authentication

137

138

#### Session Metadata

139

140

Authentication and session information for security context.

141

142

```typescript { .api }

143

interface SessionMeta {

144

/** Whether any authentication was provided with the request */

145

authenticationProvided: boolean;

146

/** Whether the provided authentication was valid */

147

authenticationSuccessful: boolean;

148

/** Type of authentication method used */

149

authType: AuthType;

150

/** Unique identifier for the user session */

151

uniqueSessionKey?: string;

152

/** Authenticated user identifier */

153

user?: string;

154

}

155

156

enum AuthType {

157

BASIC = "basic",

158

HEADER = "header",

159

JWT = "jwt",

160

SESSION_COOKIE = "session_cookie"

161

}

162

```

163

164

**Usage Examples:**

165

166

```typescript

167

import { SessionMeta, AuthType } from "@metlo/common";

168

169

// Extract session metadata from different auth types

170

function extractSessionMeta(req: any): SessionMeta {

171

// Check for JWT token

172

const authHeader = req.headers.authorization;

173

if (authHeader?.startsWith('Bearer ')) {

174

const token = authHeader.substring(7);

175

try {

176

const decoded = jwt.decode(token);

177

return {

178

authenticationProvided: true,

179

authenticationSuccessful: !!decoded,

180

authType: AuthType.JWT,

181

uniqueSessionKey: decoded?.jti || decoded?.sessionId,

182

user: decoded?.sub || decoded?.userId

183

};

184

} catch (error) {

185

return {

186

authenticationProvided: true,

187

authenticationSuccessful: false,

188

authType: AuthType.JWT

189

};

190

}

191

}

192

193

// Check for session cookie

194

const sessionCookie = req.cookies?.sessionId;

195

if (sessionCookie) {

196

return {

197

authenticationProvided: true,

198

authenticationSuccessful: true,

199

authType: AuthType.SESSION_COOKIE,

200

uniqueSessionKey: sessionCookie,

201

user: req.user?.id

202

};

203

}

204

205

// Check for basic auth

206

if (authHeader?.startsWith('Basic ')) {

207

return {

208

authenticationProvided: true,

209

authenticationSuccessful: !!req.user,

210

authType: AuthType.BASIC,

211

user: req.user?.username

212

};

213

}

214

215

// No authentication

216

return {

217

authenticationProvided: false,

218

authenticationSuccessful: false,

219

authType: AuthType.HEADER

220

};

221

}

222

```

223

224

### Platform Configuration

225

226

#### API Key Management

227

228

API key configuration for accessing Metlo backend services.

229

230

```typescript { .api }

231

interface ApiKey {

232

/** Human-readable name for the API key */

233

name: string;

234

/** Unique identifier for the key */

235

identifier: string;

236

/** When the key was created (ISO string) */

237

created: string;

238

/** What the key is used for */

239

for: API_KEY_TYPE;

240

}

241

242

enum API_KEY_TYPE {

243

GCP = "GCP",

244

AWS = "AWS",

245

GENERIC = "GENERIC",

246

ONBOARDING = "ONBOARDING"

247

}

248

```

249

250

**Usage Examples:**

251

252

```typescript

253

import { ApiKey, API_KEY_TYPE } from "@metlo/common";

254

255

// Create API keys for different integration types

256

const integrationKeys: ApiKey[] = [

257

{

258

name: "Production AWS Integration",

259

identifier: "sk_live_aws_prod_abc123",

260

created: new Date().toISOString(),

261

for: API_KEY_TYPE.AWS

262

},

263

{

264

name: "GCP Staging Environment",

265

identifier: "sk_test_gcp_staging_def456",

266

created: new Date().toISOString(),

267

for: API_KEY_TYPE.GCP

268

},

269

{

270

name: "Generic Application Key",

271

identifier: "sk_generic_app_ghi789",

272

created: new Date().toISOString(),

273

for: API_KEY_TYPE.GENERIC

274

}

275

];

276

277

// Validate API key for integration

278

function validateIntegrationKey(keyIdentifier: string, expectedType: API_KEY_TYPE): boolean {

279

const key = integrationKeys.find(k => k.identifier === keyIdentifier);

280

return key?.for === expectedType || key?.for === API_KEY_TYPE.GENERIC;

281

}

282

```

283

284

#### Connection Types

285

286

Supported integration platforms and environments.

287

288

```typescript { .api }

289

enum ConnectionType {

290

AWS = "AWS",

291

GCP = "GCP",

292

PYTHON = "PYTHON",

293

NODE = "NODE",

294

JAVA = "JAVA",

295

GOLANG = "GOLANG",

296

KUBERNETES = "KUBERNETES",

297

DOCKERCOMPOSE = "DOCKERCOMPOSE",

298

BURPSUITE = "BURPSUITE"

299

}

300

```

301

302

### Cloud Provider Integration

303

304

#### AWS Integration

305

306

AWS-specific configuration and metadata for traffic mirroring and collection.

307

308

```typescript { .api }

309

enum AWS_SOURCE_TYPE {

310

INSTANCE,

311

NETWORK_INTERFACE

312

}

313

314

// AWS-specific trace collection

315

interface AWSTraceConfig {

316

sourceType: AWS_SOURCE_TYPE;

317

instanceId?: string;

318

networkInterfaceId?: string;

319

region: string;

320

vpcId: string;

321

}

322

```

323

324

**Usage Examples:**

325

326

```typescript

327

import { AWS_SOURCE_TYPE, ConnectionType } from "@metlo/common";

328

329

// Configure AWS traffic mirroring

330

function configureAWSMirroring(config: {

331

sourceInstanceId?: string;

332

sourceENIId?: string;

333

targetENIId: string;

334

region: string;

335

}) {

336

const sourceType = config.sourceInstanceId

337

? AWS_SOURCE_TYPE.INSTANCE

338

: AWS_SOURCE_TYPE.NETWORK_INTERFACE;

339

340

return {

341

connectionType: ConnectionType.AWS,

342

sourceType,

343

source: config.sourceInstanceId || config.sourceENIId,

344

target: config.targetENIId,

345

region: config.region

346

};

347

}

348

```

349

350

#### GCP Integration

351

352

Google Cloud Platform-specific configuration for traffic collection.

353

354

```typescript { .api }

355

enum GCP_SOURCE_TYPE {

356

INSTANCE,

357

SUBNET,

358

TAG

359

}

360

361

// GCP-specific trace collection

362

interface GCPTraceConfig {

363

sourceType: GCP_SOURCE_TYPE;

364

instanceName?: string;

365

subnetName?: string;

366

networkTag?: string;

367

zone: string;

368

project: string;

369

}

370

```

371

372

**Usage Examples:**

373

374

```typescript

375

import { GCP_SOURCE_TYPE, ConnectionType } from "@metlo/common";

376

377

// Configure GCP Packet Mirroring

378

function configureGCPMirroring(config: {

379

sourceInstance?: string;

380

sourceSubnet?: string;

381

sourceTag?: string;

382

zone: string;

383

project: string;

384

}) {

385

let sourceType: GCP_SOURCE_TYPE;

386

let source: string;

387

388

if (config.sourceInstance) {

389

sourceType = GCP_SOURCE_TYPE.INSTANCE;

390

source = config.sourceInstance;

391

} else if (config.sourceSubnet) {

392

sourceType = GCP_SOURCE_TYPE.SUBNET;

393

source = config.sourceSubnet;

394

} else if (config.sourceTag) {

395

sourceType = GCP_SOURCE_TYPE.TAG;

396

source = config.sourceTag;

397

} else {

398

throw new Error("Must specify source instance, subnet, or tag");

399

}

400

401

return {

402

connectionType: ConnectionType.GCP,

403

sourceType,

404

source,

405

zone: config.zone,

406

project: config.project

407

};

408

}

409

```

410

411

### Ingestor Implementation Examples

412

413

#### Node.js/Express Middleware

414

415

```typescript

416

import express from 'express';

417

import { TraceParams, RestMethod, AnalysisType } from '@metlo/common';

418

419

// Metlo middleware for Express.js

420

function metloMiddleware(options: {

421

backendUrl: string;

422

apiKey: string;

423

redactSensitive?: boolean;

424

}) {

425

return (req: express.Request, res: express.Response, next: express.NextFunction) => {

426

const originalSend = res.send;

427

let responseBody = '';

428

429

// Capture response body

430

res.send = function(body: any) {

431

responseBody = typeof body === 'string' ? body : JSON.stringify(body);

432

return originalSend.call(this, body);

433

};

434

435

// Capture request completion

436

res.on('finish', async () => {

437

const traceParams: TraceParams = {

438

request: {

439

url: {

440

host: req.get('host') || 'unknown',

441

path: req.path,

442

parameters: Object.entries(req.query).map(([name, value]) => ({

443

name,

444

value: String(value)

445

}))

446

},

447

headers: Object.entries(req.headers).map(([name, value]) => ({

448

name,

449

value: Array.isArray(value) ? value.join(', ') : String(value)

450

})),

451

body: req.method !== 'GET' ? JSON.stringify(req.body) : '',

452

method: req.method as RestMethod

453

},

454

response: {

455

status: res.statusCode,

456

headers: Object.entries(res.getHeaders()).map(([name, value]) => ({

457

name,

458

value: String(value)

459

})),

460

body: responseBody

461

},

462

meta: {

463

incoming: true,

464

source: req.ip || req.socket.remoteAddress || 'unknown',

465

sourcePort: String(req.socket.remotePort || 0),

466

destination: req.socket.localAddress || 'unknown',

467

destinationPort: String(req.socket.localPort || 0),

468

originalSource: req.headers['x-forwarded-for'] as string || undefined

469

},

470

analysisType: AnalysisType.FULL,

471

redacted: options.redactSensitive

472

};

473

474

// Send to Metlo backend

475

try {

476

await sendTraceToMetlo(traceParams, options);

477

} catch (error) {

478

console.error('Failed to send trace to Metlo:', error);

479

}

480

});

481

482

next();

483

};

484

}

485

486

async function sendTraceToMetlo(trace: TraceParams, options: { backendUrl: string; apiKey: string }) {

487

const response = await fetch(`${options.backendUrl}/api/v1/log-request`, {

488

method: 'POST',

489

headers: {

490

'Content-Type': 'application/json',

491

'Authorization': `Bearer ${options.apiKey}`

492

},

493

body: JSON.stringify(trace)

494

});

495

496

if (!response.ok) {

497

throw new Error(`HTTP ${response.status}: ${response.statusText}`);

498

}

499

}

500

```

501

502

#### Python Flask Integration

503

504

```python

505

from flask import Flask, request, g

506

import json

507

import time

508

import requests

509

from typing import Dict, Any, List

510

511

class MetloFlaskIntegration:

512

def __init__(self, app: Flask, backend_url: str, api_key: str):

513

self.app = app

514

self.backend_url = backend_url

515

self.api_key = api_key

516

517

# Install hooks

518

app.before_request(self.before_request)

519

app.after_request(self.after_request)

520

521

def before_request(self):

522

g.metlo_start_time = time.time()

523

g.metlo_request_data = {

524

'url': {

525

'host': request.host,

526

'path': request.path,

527

'parameters': [

528

{'name': k, 'value': v}

529

for k, v in request.args.items()

530

]

531

},

532

'headers': [

533

{'name': k, 'value': v}

534

for k, v in request.headers.items()

535

],

536

'body': request.get_data(as_text=True) if request.data else '',

537

'method': request.method

538

}

539

540

def after_request(self, response):

541

if hasattr(g, 'metlo_request_data'):

542

trace_params = {

543

'request': g.metlo_request_data,

544

'response': {

545

'status': response.status_code,

546

'headers': [

547

{'name': k, 'value': v}

548

for k, v in response.headers.items()

549

],

550

'body': response.get_data(as_text=True)

551

},

552

'meta': {

553

'incoming': True,

554

'source': request.remote_addr or 'unknown',

555

'sourcePort': str(request.environ.get('REMOTE_PORT', 0)),

556

'destination': request.environ.get('SERVER_NAME', 'unknown'),

557

'destinationPort': str(request.environ.get('SERVER_PORT', 0)),

558

'originalSource': request.headers.get('X-Forwarded-For')

559

},

560

'analysisType': 'full'

561

}

562

563

# Send asynchronously to avoid blocking response

564

self._send_trace_async(trace_params)

565

566

return response

567

568

def _send_trace_async(self, trace_params: Dict[str, Any]):

569

try:

570

requests.post(

571

f"{self.backend_url}/api/v1/log-request",

572

json=trace_params,

573

headers={

574

'Content-Type': 'application/json',

575

'Authorization': f'Bearer {self.api_key}'

576

},

577

timeout=5

578

)

579

except Exception as e:

580

print(f"Failed to send trace to Metlo: {e}")

581

582

# Usage

583

app = Flask(__name__)

584

metlo = MetloFlaskIntegration(

585

app,

586

backend_url="https://your-metlo-backend.com",

587

api_key="your-api-key"

588

)

589

```

590

591

### Configuration Management

592

593

#### Webhook Configuration

594

595

Configure webhooks for alert notifications and integrations.

596

597

```typescript { .api }

598

interface WebhookResp {

599

/** Webhook configuration UUID */

600

uuid: string;

601

/** When the webhook was created */

602

createdAt: Date;

603

/** Webhook endpoint URL */

604

url: string;

605

/** Maximum retry attempts for failed deliveries */

606

maxRetries: number;

607

/** Alert types that trigger this webhook */

608

alertTypes: AlertType[];

609

/** Hosts that trigger this webhook */

610

hosts: string[];

611

/** Recent webhook execution results */

612

runs: WebhookRun[];

613

}

614

615

interface WebhookRun {

616

/** Whether the webhook delivery succeeded */

617

ok: boolean;

618

/** Result message */

619

msg: string;

620

/** Alert payload that was sent */

621

payload: Alert;

622

}

623

```

624

625

**Usage Examples:**

626

627

```typescript

628

import { WebhookResp, AlertType } from "@metlo/common";

629

630

// Configure webhook for critical security alerts

631

const securityWebhook: Partial<WebhookResp> = {

632

url: "https://your-security-system.com/webhook/metlo",

633

maxRetries: 3,

634

alertTypes: [

635

AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA,

636

AlertType.PII_DATA_DETECTED,

637

AlertType.BASIC_AUTHENTICATION_DETECTED

638

],

639

hosts: ["api.production.com", "secure.internal.com"]

640

};

641

642

// Configure Slack webhook for team notifications

643

const slackWebhook: Partial<WebhookResp> = {

644

url: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",

645

maxRetries: 2,

646

alertTypes: [AlertType.NEW_ENDPOINT, AlertType.OPEN_API_SPEC_DIFF],

647

hosts: [] // All hosts

648

};

649

```

650

651

#### Instance Settings

652

653

Global platform configuration and settings.

654

655

```typescript { .api }

656

interface InstanceSettings {

657

/** Settings UUID */

658

uuid: string;

659

/** Email for update notifications */

660

updateEmail: string;

661

/** Whether update email notifications are skipped */

662

skippedUpdateEmail: boolean;

663

}

664

```

665

666

### Performance Optimization

667

668

#### Test Generation Parameters

669

670

Configure test generation for API endpoints.

671

672

```typescript { .api }

673

interface GenerateTestParams {

674

/** Type of test to generate */

675

type: string;

676

/** Target endpoint path */

677

endpoint: string;

678

/** Template version to use */

679

version?: number;

680

/** Host for the endpoint */

681

host?: string;

682

}

683

684

interface GenerateTestRes {

685

/** Whether test generation succeeded */

686

success: boolean;

687

/** Generated template name */

688

templateName?: string;

689

/** Template version used */

690

templateVersion?: number;

691

/** Error message if generation failed */

692

msg?: string;

693

/** Generated test configuration */

694

test?: TestConfig;

695

}

696

```

697

698

**Usage Examples:**

699

700

```typescript

701

import { GenerateTestParams, GenerateTestRes } from "@metlo/common";

702

703

// Generate authentication tests for critical endpoints

704

async function generateEndpointTests(endpoints: string[]) {

705

const testResults: GenerateTestRes[] = [];

706

707

for (const endpoint of endpoints) {

708

const params: GenerateTestParams = {

709

type: "auth",

710

endpoint,

711

host: "api.production.com",

712

version: 2

713

};

714

715

try {

716

const result = await generateTest(params);

717

testResults.push(result);

718

719

if (result.success && result.test) {

720

console.log(`Generated test: ${result.templateName} v${result.templateVersion}`);

721

} else {

722

console.error(`Failed to generate test for ${endpoint}: ${result.msg}`);

723

}

724

} catch (error) {

725

console.error(`Error generating test for ${endpoint}:`, error);

726

}

727

}

728

729

return testResults;

730

}

731

732

// Batch generate different test types

733

async function generateComprehensiveTests(endpoint: string, host: string) {

734

const testTypes = ["auth", "sqli", "xss", "bola"];

735

736

for (const type of testTypes) {

737

await generateTest({

738

type,

739

endpoint,

740

host,

741

version: 1

742

});

743

}

744

}

745

```