Skip to content

Security: NelakaWith/pulse-server

Security

docs/SECURITY.md

Security Guide

Overview

This document outlines the security features implemented in Pulse Server and best practices for deploying to production.

Authentication

API Key Authentication

The server supports API Key-based authentication to protect endpoints from unauthorized access.

Configuration

Enable API Key authentication by setting these environment variables:

# Enable API key validation
API_KEY_AUTH_ENABLED=true

# Comma-separated list of valid API keys
API_KEYS=pulse-dev-key-123,pulse-test-key-456,sk-demo-key-789

Using API Keys

Provide your API key in one of two ways:

Option 1: Request Header (Recommended)

curl -H "X-API-Key: pulse-dev-key-123" \
  http://localhost:3000/api/enrichment

Option 2: Query Parameter

curl "http://localhost:3000/api/enrichment?api_key=pulse-dev-key-123"

JavaScript Example

// With header
const response = await fetch("http://localhost:3000/api/enrichment", {
  method: "POST",
  headers: {
    "X-API-Key": "pulse-dev-key-123",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    owner: "user",
    name: "repo",
    scope: "repo",
    task: "analyze",
  }),
});

// With query parameter
const response = await fetch(
  "http://localhost:3000/api/enrichment?api_key=pulse-dev-key-123",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      owner: "user",
      name: "repo",
      scope: "repo",
      task: "analyze",
    }),
  }
);

React Hook Example

// Custom hook for authenticated API calls
import { useState } from "react";

export function useApi(apiKey) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const call = async (endpoint, options = {}) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`http://localhost:3000/api${endpoint}`, {
        ...options,
        headers: {
          "X-API-Key": apiKey,
          "Content-Type": "application/json",
          ...options.headers,
        },
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || `HTTP ${response.status}`);
      }

      return await response.json();
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  };

  return { call, loading, error };
}

// Usage in component
function AnalyzeRepo() {
  const { call, loading, error } = useApi("pulse-dev-key-123");

  const handleAnalyze = async () => {
    try {
      const result = await call("/enrichment", {
        method: "POST",
        body: JSON.stringify({
          owner: "torvalds",
          name: "linux",
          scope: "repo",
          task: "analyze",
        }),
      });
      console.log(result);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <button onClick={handleAnalyze} disabled={loading}>
      {loading ? "Analyzing..." : "Analyze Repository"}
    </button>
  );
}

Rate Limiting

Rate limiting is automatically applied to all API endpoints to prevent abuse.

Limits

  • Unauthenticated (by IP): 100 requests per 15 minutes
  • Authenticated (by API Key): 500 requests per 15 minutes

Response Headers

Rate limit information is included in response headers:

X-RateLimit-Limit: 100              # Maximum requests allowed
X-RateLimit-Remaining: 87           # Remaining requests in window
X-RateLimit-Reset: 2025-11-06...    # When the window resets (ISO 8601)

Rate Limit Exceeded (429 Response)

{
  "success": false,
  "error": "Too many requests. Limit: 100 requests per 15 minutes.",
  "retryAfter": 312
}

The retryAfter value is in seconds. Wait this long before making another request.

Error Responses

Missing API Key (401)

{
  "success": false,
  "error": "Access denied. API key required. Provide via X-API-Key header or ?api_key=xxx query parameter."
}

Invalid API Key (403)

{
  "success": false,
  "error": "Invalid API key."
}

Logging

All authentication and rate limiting events are logged:

WARN: API request rejected: Missing API key from 192.168.1.1
WARN: API request rejected: Invalid API key from 192.168.1.1
WARN: Rate limit exceeded for key:pulse-dev-key-123: 500/500 requests in 900000ms
DEBUG: API request authenticated with key: pulse-dev...

Set LOG_LEVEL=debug in .env.local to see all authentication details.

Production Security Recommendations

1. HTTPS Only

Always use HTTPS in production. Redirect HTTP to HTTPS:

// In app.js or server.js
if (config.isProduction) {
  app.use((req, res, next) => {
    if (req.header("x-forwarded-proto") !== "https") {
      res.redirect(`https://${req.header("host")}${req.originalUrl}`);
    }
    next();
  });
}

2. API Key Management

  • Never commit API keys to version control
  • Use secure environment management:
    • Docker: Secrets (Docker Swarm/Kubernetes)
    • Cloud: AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager
    • On-premise: HashiCorp Vault, encrypted environment files
  • Rotate API keys regularly (e.g., every 3-6 months)
  • Create separate keys for each environment (dev, staging, prod)
  • Monitor API key usage for suspicious activity

3. CORS Configuration

Restrict CORS to trusted domains:

# .env.local
CORS_ORIGIN=https://app.example.com,https://admin.example.com

4. Input Validation

Validate all request parameters:

const schema = Joi.object({
  owner: Joi.string().alphanum().min(1).max(50).required(),
  name: Joi.string().alphanum().min(1).max(100).required(),
  scope: Joi.string().valid("repo").required(),
  task: Joi.string().valid("analyze", "summarize-issues").required(),
});

5. Monitoring & Alerts

Monitor for:

  • Multiple failed API key attempts → Alert
  • Sudden spike in rate limit hits → Alert
  • Errors from unusual IP addresses → Alert
  • Unusual request patterns → Investigate

6. Database/Token Storage

For storing API keys in a database:

// Hash API keys before storing
import crypto from "crypto";

function hashApiKey(apiKey) {
  return crypto.createHash("sha256").update(apiKey).digest("hex");
}

// Store hashed version in DB
db.apiKeys.insert({
  hashedKey: hashApiKey("pulse-dev-key-123"),
  name: "Development Key",
  owner: "dev-team",
  created: new Date(),
  lastUsed: null,
  active: true,
});

// During validation, hash incoming key and compare
const incomingHash = hashApiKey(req.header("X-API-Key"));
const valid = await db.apiKeys.findOne({
  hashedKey: incomingHash,
  active: true,
});

7. Future: JWT Implementation

For more advanced use cases, implement JWT tokens:

import jwt from "jsonwebtoken";

// Generate token (after API key validation)
const token = jwt.sign(
  { apiKey: "pulse-dev-key-123", scope: "admin" },
  process.env.JWT_SECRET,
  { expiresIn: "24h" }
);

// Validate token in middleware
export const verifyJwt = (req, res, next) => {
  const token = req.header("Authorization")?.replace("Bearer ", "");
  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(403).json({ error: "Invalid token" });
  }
};

Security Checklist

Before deploying to production:

  • API_KEY_AUTH_ENABLED=true in production .env
  • CORS_ORIGIN restricted to your domain(s)
  • HTTPS enabled and HTTP redirects to HTTPS
  • API keys stored securely (not in version control)
  • Rate limiting tested and appropriate for your use case
  • Error messages don't leak sensitive information
  • Logging includes all auth failures
  • Regular security audits scheduled
  • API keys rotated regularly
  • Monitoring and alerts configured
  • Backup and disaster recovery plan in place
  • Rate limits adjusted based on usage patterns

Testing Authentication

Test the API key system:

# ✅ Valid API key (should work)
curl -H "X-API-Key: pulse-dev-key-123" \
  http://localhost:3000/api/enrichment

# ❌ Missing API key (should fail with 401)
curl http://localhost:3000/api/enrichment

# ❌ Invalid API key (should fail with 403)
curl -H "X-API-Key: invalid-key" \
  http://localhost:3000/api/enrichment

Questions?

See other documentation:

There aren’t any published security advisories