http

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2026 License: GPL-3.0 Imports: 14 Imported by: 0

README

internal/http

HTTP API server for contextd, providing secret scrubbing endpoints for Claude Code hooks.

Overview

This package implements an Echo-based HTTP server that exposes the secret scrubbing functionality via REST endpoints. It's designed to be called by Claude Code hooks to process tool output and redact secrets before they enter the context.

Features

  • POST /api/v1/scrub - Scrub secrets from text content
  • GET /health - Health check endpoint
  • Request ID tracking
  • Request/response logging
  • Panic recovery middleware
  • Graceful shutdown support

API Reference

POST /api/v1/scrub

Scrubs secrets from the provided content using the gitleaks-based scrubber.

Request:

{
  "content": "my api key is sk-abc123..."
}

Response:

{
  "content": "my api key is [REDACTED]...",
  "findings_count": 1
}

Status Codes:

  • 200 OK - Success
  • 400 Bad Request - Invalid request body or missing content field
  • 500 Internal Server Error - Server error
GET /health

Simple health check endpoint.

Response:

{
  "status": "ok"
}

Status Codes:

  • 200 OK - Server is healthy

Usage

Basic Setup
package main

import (
    "context"
    "time"

    httpserver "github.com/fyrsmithlabs/contextd/internal/http"
    "github.com/fyrsmithlabs/contextd/internal/secrets"
    "go.uber.org/zap"
)

func main() {
    // Create scrubber
    scrubber, err := secrets.New(nil)
    if err != nil {
        panic(err)
    }

    // Create logger
    logger, _ := zap.NewProduction()
    defer logger.Sync()

    // Configure server
    cfg := &httpserver.Config{
        Host: "localhost",
        Port: 9090,
    }

    // Create server
    server, err := httpserver.NewServer(scrubber, logger, cfg)
    if err != nil {
        panic(err)
    }

    // Start server
    if err := server.Start(); err != nil {
        logger.Fatal("server error", zap.Error(err))
    }
}
With Graceful Shutdown
// Start server in background
go func() {
    if err := server.Start(); err != nil {
        logger.Error("server error", zap.Error(err))
    }
}()

// Wait for interrupt signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan

// Graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

if err := server.Shutdown(ctx); err != nil {
    logger.Error("shutdown error", zap.Error(err))
}

Configuration

The server accepts the following configuration:

type Config struct {
    Host string  // Server host (default: "localhost")
    Port int     // Server port (default: 9090)
}

Testing

Run the test suite:

go test ./internal/http/...

Check test coverage:

go test -coverprofile=cover.out ./internal/http/...
go tool cover -html=cover.out

Current coverage: 100%

Integration with Claude Code Hooks

This HTTP server is designed to be called by Claude Code hooks. Example hook configuration:

# .claude/hooks/post-tool.yaml
url: http://localhost:9090/api/v1/scrub
method: POST
body:
  content: "{{tool_output}}"

When a tool executes, Claude Code will:

  1. Execute the tool and capture output
  2. Send output to the scrub endpoint
  3. Receive scrubbed content
  4. Use scrubbed content in the context

Dependencies

  • Echo v4: HTTP framework
  • Zap: Structured logging
  • gitleaks: Secret detection (via internal/secrets)

Middleware

The server includes the following middleware:

  1. Recovery: Recovers from panics and returns 500 errors
  2. RequestID: Adds unique request IDs to all responses
  3. Logging: Logs all HTTP requests with duration and status

Performance

The server is optimized for low latency:

  • Request processing: < 10ms (excluding scrubbing time)
  • Scrubbing performance: < 100ms for 1KB content
  • Concurrent request handling via Echo's HTTP/2 support

Error Handling

The server follows REST error handling conventions:

  • 400 Bad Request - Client errors (invalid JSON, missing fields)
  • 500 Internal Server Error - Server errors (logged for debugging)

All errors include a JSON body with a message field.

Logging

The server logs all requests with:

  • HTTP method
  • Request URI
  • Status code
  • Duration
  • Request ID

Example log output:

{
  "level": "info",
  "ts": "2025-11-30T14:27:26.793Z",
  "msg": "http request",
  "method": "POST",
  "uri": "/api/v1/scrub",
  "status": 200,
  "duration": 0.012345,
  "request_id": "abc123def456"
}

Security

  • Localhost only: Server binds to localhost by default
  • No authentication: Currently designed for localhost-only access
  • Secret scrubbing: All content is scrubbed before returning
  • Input validation: Request bodies are validated

Future Enhancements

Potential future additions (not yet implemented):

  • Authentication/authorization
  • Rate limiting
  • HTTPS/TLS support
  • Additional endpoints (batch scrubbing, config updates)
  • Metrics endpoint (Prometheus-compatible)

Documentation

Overview

Package http provides HTTP server functionality for contextd.

Package http provides HTTP API for contextd.

Package http provides HTTP API for contextd.

Index

Examples

Constants

View Source
const (
	// CheckpointNameMaxLength is the UI display limit for checkpoint names.
	CheckpointNameMaxLength = 50
	// CheckpointNameTruncationSuffix is added when names are truncated.
	CheckpointNameTruncationSuffix = "..."
	// MaxSummaryLength is the maximum length for summary fields.
	MaxSummaryLength = 10000
	// MaxContextLength is the maximum length for context fields.
	MaxContextLength = 50000
	// MinThresholdPercent is the minimum valid threshold percentage.
	MinThresholdPercent = 1
	// MaxThresholdPercent is the maximum valid threshold percentage.
	MaxThresholdPercent = 100
)

Variables

This section is empty.

Functions

func CountFromCollections added in v0.3.0

func CountFromCollections(ctx context.Context, store vectorstore.Store) (checkpoints int, memories int)

CountFromCollections counts checkpoints and memories from vector store collections.

Collection names follow tenant naming conventions:

  • org_checkpoints, org_memories
  • {team}_checkpoints, {team}_memories
  • {team}_{project}_checkpoints, {team}_{project}_memories

Returns (-1, -1) if:

  • store is nil
  • listing collections fails
  • collections list is empty (chromem lazy loading case)

Otherwise returns the sum of point counts for matching collections.

Types

type CompressionStatus added in v0.3.0

type CompressionStatus struct {
	LastRatio       float64 `json:"last_ratio"`
	LastQuality     float64 `json:"last_quality"`
	OperationsTotal int64   `json:"operations_total"`
}

CompressionStatus contains compression metrics.

type Config

type Config struct {
	Host    string
	Port    int
	Version string
}

Config holds HTTP server configuration.

type ContextStatus added in v0.3.0

type ContextStatus struct {
	UsagePercent     int  `json:"usage_percent"`
	ThresholdWarning bool `json:"threshold_warning"`
}

ContextStatus contains context usage information.

type HealthResponse

type HealthResponse struct {
	Status string `json:"status"`
}

HealthResponse is the response body for GET /health.

type MemoryStatus added in v0.3.0

type MemoryStatus struct {
	LastConfidence float64 `json:"last_confidence"`
}

MemoryStatus contains memory/reasoning bank metrics.

type ScrubRequest

type ScrubRequest struct {
	Content string `json:"content"`
}

ScrubRequest is the request body for POST /api/v1/scrub.

type ScrubResponse

type ScrubResponse struct {
	Content       string `json:"content"`
	FindingsCount int    `json:"findings_count"`
}

ScrubResponse is the response body for POST /api/v1/scrub.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server provides HTTP endpoints for contextd.

Example

ExampleServer demonstrates how to create and start the HTTP server.

package main

import (
	"context"
	"fmt"
	"time"

	httpserver "github.com/fyrsmithlabs/contextd/internal/http"
	"github.com/fyrsmithlabs/contextd/internal/secrets"
	"github.com/fyrsmithlabs/contextd/internal/services"
	"go.uber.org/zap"
)

func main() {
	// Create a scrubber with default configuration
	scrubber, err := secrets.New(nil)
	if err != nil {
		panic(err)
	}

	// Create a services registry with minimal configuration
	// In a real application, you would initialize all services
	registry := services.NewRegistry(services.Options{
		Scrubber: scrubber,
		// Other services would be initialized here
	})

	// Create logger
	logger, _ := zap.NewProduction()
	defer func() { _ = logger.Sync() }()

	// Configure the server
	cfg := &httpserver.Config{
		Host: "localhost",
		Port: 9090,
	}

	// Create the server
	server, err := httpserver.NewServer(registry, logger, cfg)
	if err != nil {
		panic(err)
	}

	// Start server in background
	go func() {
		if err := server.Start(); err != nil {
			logger.Error("server error", zap.Error(err))
		}
	}()

	// Give server time to start
	time.Sleep(100 * time.Millisecond)

	// Graceful shutdown
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		logger.Error("shutdown error", zap.Error(err))
	}

	fmt.Println("Server started and stopped successfully")
}
Output:

Server started and stopped successfully

func NewServer

func NewServer(registry services.Registry, logger *zap.Logger, cfg *Config) (*Server, error)

NewServer creates a new HTTP server.

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the server.

func (*Server) Start

func (s *Server) Start() error

Start starts the HTTP server.

type StatusCounts added in v0.3.0

type StatusCounts struct {
	Checkpoints int `json:"checkpoints"`
	Memories    int `json:"memories"`
}

StatusCounts contains count information for various resources.

type StatusResponse added in v0.3.0

type StatusResponse struct {
	Status      string             `json:"status"`
	Version     string             `json:"version,omitempty"`
	Services    map[string]string  `json:"services"`
	Counts      StatusCounts       `json:"counts"`
	Context     *ContextStatus     `json:"context,omitempty"`
	Compression *CompressionStatus `json:"compression,omitempty"`
	Memory      *MemoryStatus      `json:"memory,omitempty"`
}

StatusResponse is the response body for GET /api/v1/status.

type ThresholdRequest

type ThresholdRequest struct {
	ProjectID   string `json:"project_id"`
	SessionID   string `json:"session_id"`
	Percent     int    `json:"percent"`
	Summary     string `json:"summary,omitempty"`      // Brief summary of session work (recommended)
	Context     string `json:"context,omitempty"`      // Additional context for resumption
	ProjectPath string `json:"project_path,omitempty"` // Full project path (defaults to project_id)
}

ThresholdRequest is the request body for POST /api/v1/threshold.

type ThresholdResponse

type ThresholdResponse struct {
	CheckpointID string `json:"checkpoint_id"`
	Message      string `json:"message"`
}

ThresholdResponse is the response body for POST /api/v1/threshold.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL