To follow this guide you need a SafeDep Cloud API Key and Tenant Identifier. See Cloud Quickstart on how to onboard to SafeDep Cloud and get an API key.

In this guide, we’ll use TypeScript to build a client application that leverages SafeDep Insights API v2 to identify open source package security metadata. Any programming language supported by our API SDK can be used to achieve the same results.

Project Setup

Initialize TypeScript Project

Start by creating a new TypeScript project:

npm init -y
npm install --save-dev typescript @types/node

Configure Buf Registry

Configure npm to use the Buf Registry for SafeDep API SDKs:

npm config set @buf:registry https://buf.build/gen/npm/v1/

Install SafeDep API SDKs

Install the required SafeDep API libraries:

npm install --save @buf/safedep_api.connectrpc_es@latest

Authentication Setup

Environment Variables

Set your SafeDep Cloud credentials:

export SAFEDEP_API_KEY=your-api-key
export SAFEDEP_TENANT_ID=your-tenant-id

Never hardcode API keys in your source code. Always use environment variables or secure configuration management.

Implementation

Import Dependencies

Set up the necessary imports for ConnectRPC client and SafeDep services:

import { createPromiseClient, Interceptor } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { InsightService } from "@buf/safedep_api.connectrpc_es/safedep/services/insights/v2/insights_connect.js";
import { Ecosystem } from "@buf/safedep_api.bufbuild_es/safedep/messages/package/v1/ecosystem_pb.js";

Authentication Interceptor

Create an interceptor to add authentication headers to each API call:

function authenticationInterceptor(token: string, tenant: string): Interceptor {
  return (next) => async (req) => {
    req.header.set("authorization", token);
    req.header.set("x-tenant-id", tenant);
    return await next(req);
  }
}

Main Application Logic

Implement the main function to query package insights:

async function main() {
  // Validate environment variables
  const token = process.env.SAFEDEP_API_KEY;
  if (!token) {
    console.error("SAFEDEP_API_KEY is required");
    process.exit(1);
  }

  const tenantId = process.env.SAFEDEP_TENANT_ID;
  if (!tenantId) {
    console.error("SAFEDEP_TENANT_ID is required");
    process.exit(1);
  }

  // Create transport with authentication
  const transport = createConnectTransport({
    baseUrl: "https://api.safedep.io",
    httpVersion: "1.1",
    interceptors: [authenticationInterceptor(token, tenantId)]
  });

  // Create client and make API call
  const client = createPromiseClient(InsightService, transport);
  const res = await client.getPackageVersionInsight({
    packageVersion: {
      package: {
        ecosystem: Ecosystem.NPM,
        name: "lodash",
      },
      version: "4.17.21",
    }
  });

  console.log(res.toJson());
}

// Run the application
main().catch(console.error);

Advanced Examples

Batch Package Analysis

Analyze multiple packages in a single application:

async function analyzePackages(packages: Array<{ecosystem: Ecosystem, name: string, version: string}>) {
  const client = createPromiseClient(InsightService, transport);
  const results = [];

  for (const pkg of packages) {
    try {
      const res = await client.getPackageVersionInsight({
        packageVersion: {
          package: {
            ecosystem: pkg.ecosystem,
            name: pkg.name,
          },
          version: pkg.version,
        }
      });
      results.push({
        package: `${pkg.name}@${pkg.version}`,
        insights: res.toJson()
      });
    } catch (error) {
      console.error(`Error analyzing ${pkg.name}@${pkg.version}:`, error);
    }
  }

  return results;
}

// Usage
const packagesToAnalyze = [
  { ecosystem: Ecosystem.NPM, name: "express", version: "4.18.2" },
  { ecosystem: Ecosystem.NPM, name: "react", version: "18.2.0" },
  { ecosystem: Ecosystem.PYPI, name: "requests", version: "2.31.0" }
];

analyzePackages(packagesToAnalyze).then(results => {
  console.log("Analysis results:", JSON.stringify(results, null, 2));
});

Vulnerability Filtering

Filter packages by vulnerability severity:

interface VulnerabilityReport {
  package: string;
  criticalVulns: number;
  highVulns: number;
  mediumVulns: number;
  lowVulns: number;
}

async function getVulnerabilityReport(packages: Array<{ecosystem: Ecosystem, name: string, version: string}>): Promise<VulnerabilityReport[]> {
  const client = createPromiseClient(InsightService, transport);
  const reports: VulnerabilityReport[] = [];

  for (const pkg of packages) {
    const res = await client.getPackageVersionInsight({
      packageVersion: {
        package: { ecosystem: pkg.ecosystem, name: pkg.name },
        version: pkg.version,
      }
    });

    const insights = res.toJson();
    const vulns = insights.vulnerabilities || [];

    reports.push({
      package: `${pkg.name}@${pkg.version}`,
      criticalVulns: vulns.filter(v => v.severity === 'CRITICAL').length,
      highVulns: vulns.filter(v => v.severity === 'HIGH').length,
      mediumVulns: vulns.filter(v => v.severity === 'MEDIUM').length,
      lowVulns: vulns.filter(v => v.severity === 'LOW').length,
    });
  }

  return reports;
}

Error Handling and Retry Logic

Implement robust error handling:

async function getPackageInsightWithRetry(
  client: any, 
  packageInfo: any, 
  maxRetries: number = 3
) {
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.getPackageVersionInsight(packageInfo);
    } catch (error) {
      lastError = error;
      console.warn(`Attempt ${attempt} failed:`, error.message);
      
      if (attempt < maxRetries) {
        // Exponential backoff
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError;
}

Integration Examples

Express.js Web API

Create a web API that exposes package insights:

import express from 'express';

const app = express();
app.use(express.json());

app.post('/api/package/insights', async (req, res) => {
  try {
    const { ecosystem, name, version } = req.body;
    
    const insights = await client.getPackageVersionInsight({
      packageVersion: {
        package: { ecosystem, name },
        version,
      }
    });

    res.json(insights.toJson());
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('SafeDep Insights API server running on port 3000');
});

CLI Tool

Build a command-line tool for package analysis:

#!/usr/bin/env node
import { Command } from 'commander';

const program = new Command();

program
  .name('safedep-insights')
  .description('CLI tool for SafeDep package insights')
  .version('1.0.0');

program
  .command('analyze')
  .description('Analyze a package for security insights')
  .requiredOption('-n, --name <name>', 'Package name')
  .requiredOption('-v, --version <version>', 'Package version')
  .option('-e, --ecosystem <ecosystem>', 'Package ecosystem', 'NPM')
  .action(async (options) => {
    const ecosystem = Ecosystem[options.ecosystem.toUpperCase()];
    const insights = await getPackageInsights(ecosystem, options.name, options.version);
    console.log(JSON.stringify(insights, null, 2));
  });

program.parse();

API Reference

For complete information on request and response schemas, see the Insights v2 API Specification.

Available Ecosystems

The API supports multiple package ecosystems:

  • NPM - Node.js packages
  • PYPI - Python packages
  • MAVEN - Java/JVM packages
  • CARGO - Rust packages
  • NUGET - .NET packages
  • And more…

Response Data

The API returns comprehensive security metadata including:

  • Vulnerabilities: Known security vulnerabilities
  • Licenses: License information and compliance data
  • Scorecard: OpenSSF Scorecard metrics
  • Malware: Malware detection results
  • Metadata: Package information and statistics