Skip to content
Back to skills

2586 Dev Endpoint Ad8342d7

ASecurity

Implementation patterns for a development-only HTTP endpoint that agents can query for log entries.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 31, 2026
developmentrubynextjsnodeexpressrailsapisecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 2586-dev-endpoint_ad8342d7 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2586 Dev Endpoint Ad8342d7?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2586 Dev Endpoint Ad8342d7
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2586-dev-endpoint-ad8342d7/badge)](https://www.skillsdirectory.com/skills/tools-only-2586-dev-endpoint-ad8342d7)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Dev Log Endpoint

Implementation patterns for a development-only HTTP endpoint that agents can query for log entries.

## Table of Contents

- [Next.js App Router](#nextjs-app-router)
- [Express](#express)
- [Rails](#rails)
- [Security](#security)

## Next.js App Router

```ts
// app/api/__dev/logs/route.ts
import { NextRequest, NextResponse } from "next/server";
import { readFileSync } from "fs";

// Only available in development
if (process.env.NODE_ENV !== "development") {
  throw new Error("Dev log endpoint must not be loaded in production");
}

const LOG_FILE = process.env.LOG_FILE || "logs/app.json";
const MAX_ENTRIES = 500;

export async function GET(req: NextRequest) {
  const params = req.nextUrl.searchParams;
  const level = params.get("level");
  const path = params.get("path");
  const requestId = params.get("requestId");
  const last = Math.min(parseInt(params.get("last") || "100"), MAX_ENTRIES);
  const since = params.get("since"); // ISO timestamp

  let lines: string[];
  try {
    lines = readFileSync(LOG_FILE, "utf-8").trim().split("\n");
  } catch {
    return NextResponse.json({ error: "Log file not found", path: LOG_FILE }, { status: 404 });
  }

  let entries = lines
    .map((line) => { try { return JSON.parse(line); } catch { return null; } })
    .filter(Boolean);

  if (level) entries = entries.filter((e) => e.level === level);
  if (path) entries = entries.filter((e) => e.path?.includes(path));
  if (requestId) entries = entries.filter((e) => e.requestId === requestId);
  if (since) entries = entries.filter((e) => new Date(e.timestamp) >= new Date(since));

  entries = entries.slice(-last);

  return NextResponse.json(entries);
}
```

## Express

```ts
// routes/dev-logs.ts
import { Router, Request, Response } from "express";
import { readFileSync } from "fs";

const router = Router();
const LOG_FILE = process.env.LOG_FILE || "logs/app.json";
const MAX_ENTRIES = 500;

router.get("/__dev/logs", (req: Request, res: Response) => {
  const { level, path, requestId, last = "100", since } = req.query;
  const limit = Math.min(parseInt(last as string), MAX_ENTRIES);

  let lines: string[];
  try {
    lines = readFileSync(LOG_FILE, "utf-8").trim().split("\n");
  } catch {
    return res.status(404).json({ error: "Log file not found", path: LOG_FILE });
  }

  let entries = lines
    .map((line) => { try { return JSON.parse(line); } catch { return null; } })
    .filter(Boolean);

  if (level) entries = entries.filter((e) => e.level === level);
  if (path) entries = entries.filter((e) => e.path?.includes(path as string));
  if (requestId) entries = entries.filter((e) => e.requestId === requestId);
  if (since) entries = entries.filter((e) => new Date(e.timestamp) >= new Date(since as string));

  res.json(entries.slice(-limit));
});

export default router;

// app.ts — only register in development
if (process.env.NODE_ENV === "development") {
  app.use(devLogsRouter);
}
```

## Rails

```ruby
# config/routes.rb
if Rails.env.development?
  get '__dev/logs', to: 'dev/logs#index'
end

# app/controllers/dev/logs_controller.rb
module Dev
  class LogsController < ApplicationController
    skip_before_action :authenticate_user!, raise: false
    MAX_ENTRIES = 500

    def index
      log_path = Rails.root.join('log', 'structured.json')
      unless File.exist?(log_path)
        return render json: { error: 'Log file not found', path: log_path.to_s }, status: :not_found
      end

      lines = File.readlines(log_path).last([params.fetch(:last, 100).to_i, MAX_ENTRIES].min)
      entries = lines.filter_map { |line| JSON.parse(line) rescue nil }

      entries = entries.select { |e| e['level'] == params[:level] } if params[:level]
      entries = entries.select { |e| e['path']&.include?(params[:path]) } if params[:path]
      entries = entries.select { |e| e['request_id'] == params[:request_id] } if params[:request_id]

      if params[:since]
        cutoff = Time.parse(params[:since])
        entries = entries.select { |e| Time.parse(e['timestamp']) >= cutoff }
      end

      render json: entries
    end
  end
end
```

## Security

**The dev endpoint MUST:**
- Be disabled in production (compile-time or startup check, not runtime)
- Not require authentication (agents need unauthenticated access in dev)
- Not expose sensitive fields (redact tokens, passwords, PII before logging)
- Limit response size to prevent memory issues

**Recommended approach — fail loudly if misconfigured:**

```ts
// Startup check, not runtime check
if (process.env.NODE_ENV === "production") {
  throw new Error("FATAL: Dev log endpoint loaded in production");
}
```

This is safer than `if (env !== "development") return 403` because it prevents the route from existing at all in production.

Files in this skill

  • README.md903 B
  • skill.md4.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…